Cortex-M startup files without assembler - Page 1
Forum section: electronics / general

author: technix
author_rank: Super Contributor
timestamp: March 07, 2017, 04:01:28 pm
content: I have figured out a way to use a combination of linker scripts and C language extensions to create Cortex-M startup files without touching any assembler. Is it worth the effort? Will it be problematic down the road?Here is the general gist:Linker scripts can place symbols at critical locations like the beginning and the end of different sections for you. This includes code (which I usually don't put symbols to,) data (important,) BSS (maybe not too important,) and stack (crucial.)Library routines memcpy and memset can be used to move data in place and clear BSS, if you have marked data sections and BSS with symbols.Through the use of linker scripts, sections can be places accurately (important for the ISR)Most C compiler supports putting things, like functions, structs and arrays in specific sections.I have created assembler-free startup code for STM32F103 and STM32F303, all in this fashion. I use the free and open source GNU toolchain for this. LLVM/clang should work too if you can set up the cross compiling toolchain.

author: ataradov
author_rank: Super Contributor
timestamp: March 07, 2017, 06:08:30 pm
content: What do you mean " figured out"? This feature was the explicit design goal of the core.Here are my starter projects with no assembly in them at allhttps://github.com/ataradov/mcu-starter-projects/, but that's nothing special really, that's the expected behavior.

author: mark03
author_rank: Frequent Contributor
timestamp: March 07, 2017, 06:17:42 pm
content: I have noticed vendors differ in their use of assembler or C in their CMSIS packages, e.g. some define the interrupt handlers in assembler, others in C. It doesn't make a huge difference, but assembler syntax is more uniform across assemblers than low-level compiler directives are across compilers. E.g. it's actually easier to do everything in C from gcc than it is from IAR, because gcc supports more low-level meddling from the C source than IAR does.IMO many embedded SW people are unjustifiably allergic to assembly code. Personally, I would rather just have an assembly file than "do assembly in C" for the sake of being 100% pure. AFAIK every C toolchain has an assembler; it's good to use it when appropriate.

author: ataradov
author_rank: Super Contributor
timestamp: March 07, 2017, 06:21:08 pm
content: The only thing you need, is ability to place a symbol at a fixed offset. You don't really need any non-standard C for this, but linker must support such placement. And this will apply for assembler as well, anyway.

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: March 07, 2017, 06:24:17 pm
content: I don't like using in-line assembly in a C file. I prefer to just do it purely in assembler in a separate file.But I like writing assembly code, which is getting more and more rare these days. I've been in the embedded SW game since assembly was the only option. Good C compilers didn't exist back then, and even when they did, resources were so tight that sometimes it only made sense to write hand optimized assembler. As a result, I have no aversion to writing in assembly when needed.

author: ataradov
author_rank: Super Contributor
timestamp: March 07, 2017, 06:29:40 pm
content: But that's the thing. Assembly is not needed for Cortex-M devices. It is not a hack, it is a thing that was a bullet point in the design documents.

author: mark03
author_rank: Frequent Contributor
timestamp: March 07, 2017, 08:25:39 pm
content: Quote from: ataradov on March 07, 2017, 06:21:08 pmI think you are oversimplifying. What about weak symbols, to pick just one example? Sure, they're not strictly required, but pretty much standard in any CMSIS-compliant project. Can't do that in standard C.Anyway, how do you place a symbol at a fixed offset? I guess you could delegate this entirely to the linker script, i.e. in the linker config you say "place symbol foo at offset bar", but it's cleaner to have a user-defined section and place the section in the linker config. Then you are back to vendor-specific pragmas for telling the compiler which objects belong in which section. Not that there is anything wrong with this, but again, it's not standard C. So intelligent compromise is necessary.

author: ataradov
author_rank: Super Contributor
timestamp: March 07, 2017, 09:06:32 pm
content: Quote from: mark03 on March 07, 2017, 08:25:39 pmWeak symbols are also not a given in assembly. But you don't really need them.Quote from: mark03 on March 07, 2017, 08:25:39 pmIt is cleaner, but there is no standard assembly syntax either, so if you are worried about portability between the toolchains, then you are not solving anything. And going between IAR assembler and GCC assembler is way harder than going between IAR C and GCC C extensions.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 08, 2017, 06:27:26 am
content: Quote from: Sal Ammoniac on March 07, 2017, 06:24:17 pmI haven't written a lot of assembly (lately), but if there's just a small amount needed--usually to manipulate weird registers in the core--I'd much rather do it as asm volatile (). Putting the assembly in C/C++ form means I can use the same constants as in the main program, and also make sure the whole thing is type safe.With your assembly in a separate file, your point of integration is at the linker stage. I.e., you can write functions in assembly and call them from C. With inline functions defined with inline assembly, you can mix it up a bit more and essentially insert snippets of assembly within other functions.Plus, I can never remember what the difference between .s and .S is.

author: andersm
author_rank: Super Contributor
timestamp: March 08, 2017, 11:19:03 am
content: Quote from: andyturk on March 08, 2017, 06:27:26 amThat's still simple, what really gets people is the difference between .c and .C.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 08, 2017, 02:56:05 pm
content: Quote from: andersm on March 08, 2017, 11:19:03 am.C ?

author: janoc
author_rank: Super Contributor
timestamp: March 08, 2017, 03:33:02 pm
content: Quote from: andyturk on March 08, 2017, 02:56:05 pm.c = C code.C = C++ code - original extension for C++ still used by some compilers on case-sensitive platforms (mostly Unix). Such file will be compiled as C++ with common compilers (gcc, for ex). This could seriously trip you up if you are careless with your filenames. I haven't tried what will gcc make out of a file with .C extension in Windows (case-insensitive but case-preserving system) - it is possible it will understand it as C++ as well.In fact, the reason why we have .cpp today instead of .C is because case insensitive platforms have proliferated since C++ has appeared and there it is not possible/practical to make difference between .c and .C extensions in filenames. You could also find .cc extension for C++ code..s and .S is the same thing, assembler source.HOWEVER, the .S will get preprocessor run on it first, .s will not. Unless you are Apple user, apparently there the preprocessor will be used on both types of files. Yay ...

author: nctnico
author_rank: Super Contributor
timestamp: March 08, 2017, 03:59:22 pm
content: Quote from: ataradov on March 07, 2017, 06:08:30 pmI agree. All it takes is a memset and a memcpy to initialise the memory (bonus points for clearing the stack space as well). I have been doing that on ARM Cortex since day one. It is all in the linker script and I don't see how that can be any different on a non-GCC compiler unless it has a really crappy linker which doesn't allow to create symbols in the linker script.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 08, 2017, 04:05:08 pm
content: Quote from: janoc on March 08, 2017, 03:33:02 pmYep, I write all my code on a mac and have been bitten by the case insensitive filesystem before. I was even writing C++ back in the cfront days (also on a mac), but have forgotten what file extensions we used.I prefer .cc to .cpp. Actually, .cpp isn't that bad, but .hpp just seems wrong (and they like to go together). So I use .cc and .hOh, and back on the original topic... I've been usingX-Macrosto define the interrupt vector on my Cortex-M builds:Code:// Create a forward declaration for each interrupt handler#define TM4C_INTERRUPT(num,bit,name) extern "C" void name ## _Handler(void) __attribute__ ((weak, alias("Unused_Handler")));TM4C_INTERRUPTS#undef TM4C_INTERRUPTtypedef void (*handler_t)(void);// Create a structure definition to hold all the handlers in orderstruct cortex_interrupt_vectors_t {void *initial_msp;#define TM4C_INTERRUPT(num,bit,name) handler_t name ## _handler;TM4C_INTERRUPTS#undef TM4C_INTERRUPT} const cortex_vectors __attribute__ ((section(".vectors"), used)) = {&__main_stack_end__,#define TM4C_INTERRUPT(num,bit,name) .name ## _handler = name##_Handler,TM4C_INTERRUPTS#undef TM4C_INTERRUPTThe interrupts are defined like this:Code:#pragma once/** This macro defines the names and order of the interrupt handlers for the* TM4C1232H6PM mcu. The macro is defined once and expanded multiple times as an* X Macro (ref http://en.wikipedia.org/wiki/X_Macro).** These interrupts are defined in tm4c1232h6pm.pdf, Section 2.5.2*/// +------------------- Vector Number// |// | +-------------- Interrupt Bit Number// | |// | | +-------- Vector Name// | | |// v v v#define TM4C_INTERRUPTS \TM4C_INTERRUPT( 1, -1, Reset) \TM4C_INTERRUPT( 2, -1, NMI) \TM4C_INTERRUPT( 3, -1, HardFault) \TM4C_INTERRUPT( 4, -1, MemManage) \TM4C_INTERRUPT( 5, -1, BusFault) \TM4C_INTERRUPT( 6, -1, UsageFault) \TM4C_INTERRUPT( 7, -1, Reserved7) \TM4C_INTERRUPT( 8, -1, Reserved8) \TM4C_INTERRUPT( 9, -1, Reserved9) \TM4C_INTERRUPT( 10, -1, Reserved10) \TM4C_INTERRUPT( 11, -1, SVC) \TM4C_INTERRUPT( 12, -1, DebugMonitor) \TM4C_INTERRUPT( 13, -1, Reserved13) \TM4C_INTERRUPT( 14, -1, PendSV) \TM4C_INTERRUPT( 15, -1, SysTick) \TM4C_INTERRUPT( 16, 0, GPIOA) \TM4C_INTERRUPT( 17, 1, GPIOB) \TM4C_INTERRUPT( 18, 2, GPIOC) \TM4C_INTERRUPT( 19, 3, GPIOD) \TM4C_INTERRUPT( 20, 4, GPIOE) \TM4C_INTERRUPT( 21, 5, UART0) \TM4C_INTERRUPT( 22, 6, UART1) \TM4C_INTERRUPT( 23, 7, SSI0) \TM4C_INTERRUPT( 24, 8, I2C0) \TM4C_INTERRUPT( 25, 9, Reserved25) \TM4C_INTERRUPT( 26, 10, Reserved26) \TM4C_INTERRUPT( 27, 11, Reserved27) \TM4C_INTERRUPT( 28, 12, Reserved28) \TM4C_INTERRUPT( 29, 13, Reserved) \TM4C_INTERRUPT( 30, 14, ADC0SS0) \TM4C_INTERRUPT( 31, 15, ADC0SS1) \TM4C_INTERRUPT( 32, 16, ADC0SS2) \TM4C_INTERRUPT( 33, 17, ADC0SS3) \TM4C_INTERRUPT( 34, 18, WATCHDOG) \TM4C_INTERRUPT( 35, 19, TIMER0A) \TM4C_INTERRUPT( 36, 20, TIMER0B) \TM4C_INTERRUPT( 37, 21, TIMER1A) \TM4C_INTERRUPT( 38, 22, TIMER1B) \TM4C_INTERRUPT( 39, 23, TIMER2A) \TM4C_INTERRUPT( 40, 24, TIMER2B) \TM4C_INTERRUPT( 41, 25, COMP0) \TM4C_INTERRUPT( 42, 26, COMP1) \TM4C_INTERRUPT( 43, 27, Reserved43) \TM4C_INTERRUPT( 44, 28, SYSCTL) \TM4C_INTERRUPT( 45, 29, FLASH) \TM4C_INTERRUPT( 46, 30, GPIOF) \TM4C_INTERRUPT( 47, 31, GPIOG) \TM4C_INTERRUPT( 48, 32, Reserved48) \TM4C_INTERRUPT( 49, 33, UART2) \TM4C_INTERRUPT( 50, 34, SSI1) \TM4C_INTERRUPT( 51, 35, TIMER3A) \TM4C_INTERRUPT( 52, 36, TIMER3B) \TM4C_INTERRUPT( 53, 37, I2C1) \TM4C_INTERRUPT( 54, 38, Reserved54) \TM4C_INTERRUPT( 55, 39, CAN0) \TM4C_INTERRUPT( 56, 40, Reserved56) \TM4C_INTERRUPT( 57, 41, Reserved57) \TM4C_INTERRUPT( 58, 42, Reserved58) \TM4C_INTERRUPT( 59, 43, Reserved59) \TM4C_INTERRUPT( 60, 44, USB0) \TM4C_INTERRUPT( 61, 45, Reserved61) \TM4C_INTERRUPT( 62, 46, UDMA) \TM4C_INTERRUPT( 63, 47, UDMAERR) \TM4C_INTERRUPT( 64, 48, ADC1SS0) \TM4C_INTERRUPT( 65, 49, ADC1SS1) \TM4C_INTERRUPT( 66, 50, ADC1SS2) \TM4C_INTERRUPT( 67, 51, ADC1SS3) \TM4C_INTERRUPT( 68, 52, Reserved68) \TM4C_INTERRUPT( 69, 53, Reserved69) \TM4C_INTERRUPT( 70, 54, Reserved70) \TM4C_INTERRUPT( 71, 55, Reserved71) \TM4C_INTERRUPT( 72, 56, Reserved72) \TM4C_INTERRUPT( 73, 57, SSI2) \TM4C_INTERRUPT( 74, 58, SSI3) \TM4C_INTERRUPT( 75, 59, UART3) \TM4C_INTERRUPT( 76, 60, UART4) \TM4C_INTERRUPT( 77, 61, UART5) \TM4C_INTERRUPT( 78, 62, UART6) \TM4C_INTERRUPT( 79, 63, UART7) \TM4C_INTERRUPT( 80, 64, Reserved80) \TM4C_INTERRUPT( 81, 65, Reserved81) \TM4C_INTERRUPT( 82, 66, Reserved82) \TM4C_INTERRUPT( 83, 67, Reserved83) \TM4C_INTERRUPT( 84, 68, I2C2) \TM4C_INTERRUPT( 85, 69, I2C3) \TM4C_INTERRUPT( 86, 70, TIMER4A) \TM4C_INTERRUPT( 87, 71, TIMER4B) \TM4C_INTERRUPT( 88, 72, Reserved88) \TM4C_INTERRUPT( 89, 73, Reserved89) \TM4C_INTERRUPT( 90, 74, Reserved90) \TM4C_INTERRUPT( 91, 75, Reserved91) \TM4C_INTERRUPT( 92, 76, Reserved92) \TM4C_INTERRUPT( 93, 77, Reserved93) \TM4C_INTERRUPT( 94, 78, Reserved94) \TM4C_INTERRUPT( 95, 79, Reserved95) \TM4C_INTERRUPT( 96, 80, Reserved96) \TM4C_INTERRUPT( 97, 81, Reserved97) \TM4C_INTERRUPT( 98, 82, Reserved98) \TM4C_INTERRUPT( 99, 83, Reserved99) \TM4C_INTERRUPT(100, 84, Reserved100) \TM4C_INTERRUPT(101, 85, Reserved101) \TM4C_INTERRUPT(102, 86, Reserved102) \TM4C_INTERRUPT(103, 87, Reserved103) \TM4C_INTERRUPT(104, 88, Reserved104) \TM4C_INTERRUPT(105, 89, Reserved105) \TM4C_INTERRUPT(106, 90, Reserved106) \TM4C_INTERRUPT(107, 91, Reserved107) \TM4C_INTERRUPT(108, 92, TIMER5A) \TM4C_INTERRUPT(109, 93, TIMER5B) \TM4C_INTERRUPT(110, 94, WTIMER0A) \TM4C_INTERRUPT(111, 95, WTIMER0B) \TM4C_INTERRUPT(112, 96, WTIMER1A) \TM4C_INTERRUPT(113, 97, WTIMER1B) \TM4C_INTERRUPT(114, 98, WTIMER2A) \TM4C_INTERRUPT(115, 99, WTIMER2B) \TM4C_INTERRUPT(116, 100, WTIMER3A) \TM4C_INTERRUPT(117, 101, WTIMER3B) \TM4C_INTERRUPT(118, 102, WTIMER4A) \TM4C_INTERRUPT(119, 103, WTIMER4B) \TM4C_INTERRUPT(120, 104, WTIMER5A) \TM4C_INTERRUPT(121, 105, WTIMER5B) \TM4C_INTERRUPT(122, 106, SYSEXC) \TM4C_INTERRUPT(123, 107, Reserved123) \TM4C_INTERRUPT(124, 108, Reserved124) \TM4C_INTERRUPT(125, 109, I2C4) \TM4C_INTERRUPT(126, 110, I2C5) \TM4C_INTERRUPT(127, 111, Reserved127) \TM4C_INTERRUPT(128, 112, Reserved128) \TM4C_INTERRUPT(129, 113, Reserved129) \TM4C_INTERRUPT(130, 114, Reserved130) \TM4C_INTERRUPT(131, 115, Reserved131) \TM4C_INTERRUPT(132, 116, Reserved132) \TM4C_INTERRUPT(133, 117, Reserved133) \TM4C_INTERRUPT(134, 118, Reserved134) \TM4C_INTERRUPT(135, 119, Reserved135) \TM4C_INTERRUPT(136, 120, Reserved136) \TM4C_INTERRUPT(137, 121, Reserved137) \TM4C_INTERRUPT(138, 122, Reserved138) \TM4C_INTERRUPT(139, 123, Reserved139) \TM4C_INTERRUPT(140, 124, Reserved140) \TM4C_INTERRUPT(141, 125, Reserved141) \TM4C_INTERRUPT(142, 126, Reserved142) \TM4C_INTERRUPT(143, 127, Reserved143) \TM4C_INTERRUPT(144, 128, Reserved144) \TM4C_INTERRUPT(145, 129, Reserved145) \TM4C_INTERRUPT(146, 130, Reserved146) \TM4C_INTERRUPT(147, 131, Reserved147) \TM4C_INTERRUPT(148, 132, Reserved148) \TM4C_INTERRUPT(149, 133, Reserved149) \TM4C_INTERRUPT(150, 134, Reserved150) \TM4C_INTERRUPT(151, 135, Reserved151) \TM4C_INTERRUPT(152, 136, Reserved152) \TM4C_INTERRUPT(153, 137, Reserved153) \TM4C_INTERRUPT(154, 138, Reserved154)

author: andyturk
author_rank: Frequent Contributor
timestamp: March 08, 2017, 04:11:50 pm
content: Quote from: nctnico on March 08, 2017, 03:59:22 pmSeems like the IDE vendors like to do their own magic startup implementation, putting as much of it in their own assembler syntax to make it complicated and proprietary. That's enough to scare away most embedded coders and help lock projects into using that particular IDE.It probably made more sense when proprietary compilers were the norm, but today with GCC and ARM Cortex being nearly everywhere, the proprietary stuff doesn't add nearly as much value (they do have nice debuggers though).PS. When you "clear" the stack space... are you clearing it to 0? Or to some sentinel value for detecting overflow?

author: nctnico
author_rank: Super Contributor
timestamp: March 08, 2017, 04:42:51 pm
content: I just clear it to zero but during development I keep track on how much stack space I can use. I keep things simple so it is pretty straightforward.

author: janoc
author_rank: Super Contributor
timestamp: March 08, 2017, 04:50:19 pm
content: Quote from: andyturk on March 08, 2017, 04:05:08 pm.hpp emerged mostly because IDEs like Visual Studio or Borland/Turbo C didn't know how to figure out whether the header is C or C++ from the content. That messes up things like syntax highlighting. In fact, the standard C++ headers are without any extension, .h headers are supposed to be for C, but few projects actually follow this - especially with braindead IDEs like Visual Studio which are utterly lost if the extension is not present.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 08, 2017, 05:23:59 pm
content: Code:// -*- Mode:C++ -*-

author: technix
author_rank: Super Contributor
timestamp: March 08, 2017, 11:51:26 pm
content: My default file extension for C++ is .cc - to keep some symmetry with another language I often use on the desktop: .m and .mm for Objective-C and Objective-C++. Also I don't use .hpp or .hh but C++ headers have #ifdef __cplusplus outside #include guard.

author: westfw
author_rank: Super Contributor
timestamp: March 09, 2017, 12:19:38 am
content: >QuoteNo, I dont' think so. Even in processors that are much less C-friendly than ARM, there is usually so little assembler startup code that it's not a big deal. As others have pointed out, the assembler is probably more standardized and better documented than the (relatively minor) C compiler-dependent commands to accomplish the bits that need to happen, and it's usually the linker commands that confuse people (sadly needed for both assembler and C-based start-up code.)Like various others, I had read the "no assembler needed" boilerplate from ARM ("as long as you implement our recommended intrinsics."), and I was surprised to see various compilers that were shipping with assembly startup code anyway (Keil, now owned by ARM, for instance.)

author: ataradov
author_rank: Super Contributor
timestamp: March 09, 2017, 12:23:05 am
content: Quote from: westfw on March 09, 2017, 12:19:38 amI would 100% disagree with this statement. While actual assembly instructions are standard (UAL syntax defined by ARM), service commands are totally different among major compilers.Quote from: westfw on March 09, 2017, 12:19:38 amThis is a very annoying practice of carrying legacy code and legacy thinking.Hardware was designed to do something - take advantage of it.

author: technix
author_rank: Super Contributor
timestamp: March 09, 2017, 02:47:15 pm
content: Quote from: ataradov on March 09, 2017, 12:23:05 amI have a few dev boards based on ARM7TDMI, ARM926EJ-S and ARM1176JZF-S. For those boards there is always only one assembler file for the interrupt vector, and all remaining code is C with GCC extensions.

author: nctnico
author_rank: Super Contributor
timestamp: March 09, 2017, 03:12:50 pm
content: Those are older architectures. On those you need to setup various stack pointers using assembler. Also pushing & popping the variables on stack is not done automatically and there may be extra code needed to jump to thumb code (for example on the ARM7TDMI which executes interrupts in ARM mode).

author: janoc
author_rank: Super Contributor
timestamp: March 09, 2017, 07:47:49 pm
content: Quote from: andyturk on March 08, 2017, 05:23:59 pmAnd that works elsewhere than in Emacs?

author: westfw
author_rank: Super Contributor
timestamp: March 10, 2017, 10:04:36 am
content: QuoteI don't know. Having the interrupt controller behavior match the C abi, so you don't need special compiler behavior for ISRs, is really nice. But eliminating a half page of assembly start-up code by allowing the same thing in C seems a bit gimmicky. Especially since most of that is just a big table of ISR vectors, which is equally opaque (or not) in either C or ASM.FWIW, it looks like the startup code for ATMEL ARM chips is written entirely in C.(Hmm. I think a big factor might be that it's pretty important for startup code to be published in a form that users can use without having to buy an expensive compiler. Most vendors have some sort of free assembler, but not all of them distribute a free C compiler, so having the startup code be in ASM is a bit of an advantage...) (?)Here's the entire Atmel SAMD10 startup code, not including most of the vector table, or the magic commands that make symbols weak and otherwise easy to manipulate:Code:* Copyright (c) 2014 Atmel Corporation. All rights reserved./* Exception Table */__attribute__ ((section(".vectors")))const DeviceVectors exception_table = {/* Configure Initial Stack Pointer, using linker-generated symbols */(void*) (&_estack),(void*) Reset_Handler,(void*) NMI_Handler,: // Bunch of vector omitted.(void*) ADC_Handler, /* 15 Analog Digital Converter */(void*) AC_Handler, /* 16 Analog Comparators */(void*) DAC_Handler, /* 17 Digital Analog Converter */(void*) PTC_Handler /* 18 Peripheral Touch Controller */};/*** \brief This is the code that gets called on processor reset.* To initialize the device, and call the main() routine.*/void Reset_Handler(void){uint32_t *pSrc, *pDest;/* Initialize the relocate segment */pSrc = &_etext;pDest = &_srelocate;if (pSrc != pDest) {for (; pDest < &_erelocate;) {*pDest++ = *pSrc++;}}/* Clear the zero segment */for (pDest = &_szero; pDest < &_ezero;) {*pDest++ = 0;}/* Set the vector table base address */pSrc = (uint32_t *) & _sfixed;SCB->VTOR = ((uint32_t) pSrc & SCB_VTOR_TBLOFF_Msk);/* Initialize the C library */__libc_init_array();/* Branch to main function */main();/* Infinite loop */while (1);}void Dummy_Handler(void){while (1) {}}

author: TNorthover
author_rank: Contributor
timestamp: March 10, 2017, 02:48:02 pm
content: Quote from: westfw on March 10, 2017, 10:04:36 amUnfortunately, even that bit doesn't quite work. The ABI only guarantees 8-byte stack alignment at function call boundaries, but since interrupts can occur anywhere that means a handler must realign the stack on entry (or at least, if it's going to make any calls).You'll probably get away with it most of the time, but there's an __attribute__((interrupt("IRQ"))) if you want to be certain.

author: andersm
author_rank: Super Contributor
timestamp: March 10, 2017, 04:36:20 pm
content: Quote from: TNorthover on March 10, 2017, 02:48:02 pmSetting the CCR.STKALIGN bit ensures the stack is 8-byte aligned when entering exceptions. The bit was added in the first update (r1p0) of the Cortex-M3 core, so practically all devices have this feature, though it's not necessarily set by default.

author: ataradov
author_rank: Super Contributor
timestamp: March 10, 2017, 04:43:09 pm
content: Quote from: TNorthover on March 10, 2017, 02:48:02 pmA quote from ARMv6-M Architecture Reference Manual:QuoteNot even a bit to think about.

author: TNorthover
author_rank: Contributor
timestamp: March 10, 2017, 04:52:21 pm
content: Quote from: ataradov on March 10, 2017, 04:43:09 pmOh, thanks! I did not know that. Time to fix Clang.

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: March 10, 2017, 05:06:34 pm
content: Quote from: westfw on March 10, 2017, 10:04:36 amThis is the thing that annoys me the most. This is usually a black box doing who knows what, including dodgy stuff like dynamic memory allocation, etc. I'd like to see a much more transparent C library distributed by the vendors.

author: ataradov
author_rank: Super Contributor
timestamp: March 10, 2017, 05:14:52 pm
content: Quote from: Sal Ammoniac on March 10, 2017, 05:06:34 pmStartup code is part of your application, you can do whatever you want. I personally don't even call this in any of my startup files, because I know that I don't need this.

author: Bruce Abbott
author_rank: Frequent Contributor
timestamp: March 10, 2017, 07:31:11 pm
content: Quote from: ataradov on March 10, 2017, 05:14:52 pmSure,ifyou know what you are doing. But most vendors want you to use their frameworks and libraries - so you end upnotknowing what you are doing. And the vast majority of example code requires their stuff, so if you want to do 'whatever you want' then you have a lot to learn. That's why I am here.Quote from: ataradovSo you didn't have to "figure it out"? Good for you! But most of us are using the chip vendor's bloatware, and figuring out how to go fully 'bare metal' is not trivial for us.I have programmed the STM32F103 'bare metal' in assembler, but until now I didn't know that I could do it in C. That's why I am interested to see howtechnixdid it. However your code seems to be cleaner. What changes would be need to implement it on STM32?QuoteExcept for the FE310, which has this...Code:asm (" \.section .init._start\n \.globl _start\n \_start:\n \la gp, _gp\n \la sp, _stack_top\n \");

author: andyturk
author_rank: Frequent Contributor
timestamp: March 10, 2017, 07:35:29 pm
content: Quote from: Sal Ammoniac on March 10, 2017, 05:06:34 pmIt's generated by the linker and contains calls to all your static initializers. If you're not using C++, then (I think) you don't need to call it. If you are using C++, then __libc_init_array() will only do dodgy stuff if your constructors of static objects are doing dodgy stuff.So, just avoid doing computation, memory allocation, etc. in a constructor for any class with a static instance. My rule is to restrict constructors to using only values that are known at link time.Another way to make sure you __libc_init_array() is "clean" is to avoid creating global instances at all, and take complete control of when your constructors are called by having a single class with all your application state and constructing it via placement new.Code:/*** @brief returns a reference to the application instance* @tparam T the type of application** This template uses placement new to statically allocate* RAM for the application object, but delay its construction* until main() has started to run.*/template<class T>T &the() {alignas(T) static char memory[sizeof(T)];static T *instance;if (instance == 0) instance = new(memory) T;return *instance;}int main() {asm volatile("cpsid i"); // disable interruptsSystemInit();rtos::initialize_kernel();the<WholeApplication>().initialize();asm volatile("cpsie i"); // enable interruptsrtos::start_kernel();return 0; // NOT REACHED}

author: ataradov
author_rank: Super Contributor
timestamp: March 10, 2017, 07:35:55 pm
content: Quote from: Bruce Abbott on March 10, 2017, 07:31:11 pmI don't use vendor libraries. They are garbage in most cases anyway.Quote from: Bruce Abbott on March 10, 2017, 07:31:11 pmJust list your interrupt handlers. That's all.Quote from: Bruce Abbott on March 10, 2017, 07:31:11 pmBecause this core is stupidly designed. And the whole things feels like it came straight from the 80s as far as design goes, so assembly startup is par for the course there.

author: technix
author_rank: Super Contributor
timestamp: March 11, 2017, 03:19:14 am
content: Quote from: Bruce Abbott on March 10, 2017, 07:31:11 pmSomething like this:Code:// stm32f303cc_it.cvoid __stack(void);void __attribute__((section(".isr_vector"))) (*ISR_Vector)(void) ={__stack,Reset_Handler,NMI_Handler,// ...};void Reset_Handler(void){extern uint8_t _sidata, _sdata, _edata, _sbss, _ebss;SystemInit();memcpy(&_sdata, &_sidata, &_edata - &_sdata);memset(&_sbss, 0, &_ebss - &_sbss);_start(); // call libc}andCode:/* stm32f303cc.ld */MEMORY{IROM (rx) : org = 0x08000000, len = 0x00020000IRAM (rw) : org = 0x20000000, len = 0x00005000}SECTIONS{__stack = ORIGIN(IRAM) + LENGTH(IRAM);. = ORIGIN(IROM);.isr_vector : AT(ORIGIN(IROM)){*(.isr_vector)*(.after_vector)} > IROM/* ... */_sidata = .;.data : AT(_sidata){_sdata = .;*(.data). = ALIGN(4);_edata = .;} > IRAM.bss{_sbss = .;*(.bss)_ebss = .;} > IRAM}

author: westfw
author_rank: Super Contributor
timestamp: March 13, 2017, 10:20:54 am
content: Hmm. Does anyone have a nice SVC handler written in C? Fetching the imm8 argument looks pretty nasty :-(It doesn't even seem to be one of the ARM instructions that has a CMSIS-specified C intrinsic associated with it, at least not below the CMSIS/RTOS layer.And getting C to pass arguments to SVC the way it does to a function call (which would be nice)?

author: andersm
author_rank: Super Contributor
timestamp: March 13, 2017, 02:36:34 pm
content: Quote from: westfw on March 13, 2017, 10:20:54 amPassing and returning arguments doesn't really require any extra effort, as long as all arguments can be passed in registers. If you need more parameters, you could use a struct and pass a pointer to that. Extracting the SVC is never going to be especially pretty, and needs some assembly, see eg.here.

author: andyturk
author_rank: Frequent Contributor
timestamp: March 13, 2017, 09:21:42 pm
content: Quote from: westfw on March 13, 2017, 10:20:54 amYou can take a look at how Keil implemented RTX (their RTOS). It uses SVC calls to talk to the kernel. You'll want to be sitting down (with a stiff cocktail in hand) before looking at the code, though ...https://github.com/ARM-software/CMSIS_5/blob/develop/CMSIS/RTOS2/RTX/Source/core_cm.h#L270

author: bson
author_rank: Unknown
timestamp: March 14, 2017, 06:57:25 pm
content: Quote from: andyturk on March 08, 2017, 06:27:26 amAgreed; the other main use for assembly is small inline bits, like for example copy data with some weird coloring in a context where performance matters. Or even as simple as a mod-255 IP checksum (where it's generally done using a 32 bit accumulator for performance and then folded back). In this case assembly absolutely should be inlined, but an inline function to wrap it might be a good idea, especially for gcc so it can know about const-ness and (non-)aliasing. Also makes it easy to reimplement for a different compiler without littering the code with a bunch of #ifdef crap. Usually when I do things like this I also have a naive C implementation that can be swapped in for testing purposes. (If it can be done in C. Some things, like saving thread context if you roll your own scheduler isn't doable in C.)

author: technix
author_rank: Super Contributor
timestamp: March 14, 2017, 11:11:23 pm
content: Quote from: bson on March 14, 2017, 06:57:25 pmInline assembler for GCC (and LLVM/clang too) supports mixing C symbols in the assembler, making the coding a bit easier as you no longer need to mind the ABI, optimization level and where the variables and arguments would go.

author: westfw
author_rank: Super Contributor
timestamp: March 15, 2017, 01:25:47 am
content: One problem with "inline assembler" is it throws you pretty quickly into the morass of vendor differences in compiler syntax and behavior. (well, also true of pure assembler, I guess...)The SVC case looks especially annoying; I'd *like* the compiler to treat the SVC instruction the same way it treats BL; so that a C function "__svc(arg1, arg2, arg3, arg4, arg5, arg6...) starts by moving arguments into registers, and then sets up a stack frame if there are too many arguments, and the finally does the SVC instruction where it might have done BL for a normal function. But if I make it inline, then the argument behavior will change, and if I make it a normal function it won't be inlined the way I want, so...Quoteyeah. THAT happens.

author: technix
author_rank: Super Contributor
timestamp: March 23, 2017, 02:56:21 am
content: I never really used SVC in Cortex-M code... What does it achieve on a system without MPU?

author: westfw
author_rank: Super Contributor
timestamp: March 23, 2017, 08:32:03 am
content: Quote1) Many Cortex-M chips DO have an MPU.2) even if there is no MPU, there is still a different stack pointer, and maybe "priveledged mode".So it's potentially useful in any application where you have a clear division between "core services" and "applications", whether that's a full blown OS that uses the MPU, or a much more primitive "this is user code and this is the system code" situation.(At the moment, it's being amusing as a counter to the "you never need assembly language" boast.)

author: technix
author_rank: Super Contributor
timestamp: March 23, 2017, 09:44:20 am
content: Quote from: westfw on March 23, 2017, 08:32:03 amYet the popular STM32F1 don't have the MPU...Well since I write a lot of code with STM32F1 and I don't use an RTOS, there is no need for assembler. Should I need SVC I would still prefer inline assembler (as I am sticking to GCC anyway.)