Hi,
Being new to the fascinating ARM world, I am going through various documentation and reading material.
I read that the ARM Cortex-M processors (or I think it applies to all the Cortex processors) are entirely C programmable. This means that assembly code is not required for start-up code and for ISRs.
I am curious to find out what prohibits the earlier ARM processors (or even other architectures) to write the start-up code in C? Are these any specific instructions? One which I found was the WFI (Wait For Interrupt) which can't have an equivalent in C. But that is not a start-up or ISR instruction.
Thanks for your support !
Gopal
Hi Gopal-san,
I think that it would mean the interrupt handler of Cortex-M could be written in the same way as normal C functions. Actually to use WFI, we should use the asm statement. By the way, almost all parts of a program can be written by C. For example, the interrupt vectors which are made by the C language array of pointer to ISRs are located into address 0 by the C linker. By conventional ARM architecture (e.g. ARM7), instructions are located at the interrupt (including reset) vectors, and it would be impossible to write the startup codes by C language.
Best regards,Yasuhiko Koumoto.
yasuhikokoumoto's answer is correct. -The ARM7 requires instructions in the interrupt vectors (plus it requires a bunch of register setup if you use the FIQ interrupt and setup of stack pointers for each interrupt mode you use).
You should know that all the necessary assembly instructions for Cortex-M that you would need, are already available through the CMSIS API.
For instance, __REV(), __WFI(), __WFE(), __SEV(), __ISB(), __DSB(), __DMP() - yes even __NOP() are all available, so you do not need to use the asm directive - there are many more, see your favourite device's core_cmInstr.h for the complete list.
-But you can of course still write code in assembly language if you wish, and in some cases you may benefit from doing so.
In general, I would recommend to stick to C, because it's easier to change quickly and it's also easier to make your code portable to other Cortex-M (or even Cortex-A) platforms.
I agree that "normal" interrupt handling can be done completely in C.
But I was wonder, for SVC handler, the beginning still needs around 4 lines of inline assembly. How does this fact support the claim "entirely C"?
I am curious to find out what prohibits the earlier ARM processors (or even other architectures) to write the start-up code in C?
The C language is stack-based so a C compiler assumes there is a valid stack to use when compiling C source code. The ARM v7-M architecture implemented by Cortex-M devices defines the first entry in the vector table to be the Initial Stack pointer value which the hardware copies automatically out of reset into the SP (Stack Pointer); thus, out of reset, the start-up code can be written in C because a valid stack is available.
Earlier ARM processors like the ARM7 mentioned above, or Cortex-A and Cortex-R devices for the matter, do not have an initial stack value defined out of reset, so the start-up code cannot be written in C.
What 4 lines of inline assembly are you referring to? If you mean the code needed to pass parameters from the Supervisor call to the SVC mode interrupt handler, then the ARM Compiler offers the __svc keyword which defines a SuperVisor Call (SVC) function taking up to four integer-like arguments and returning up to four results in a value_in_regs structure.
In the beginning of SVC handler, we need to test LR, if the stacking was done using MSP or PSP. This contains basically 4 assembly instructions.
Typically, the SVC handler is part of the embedded OS / RTOS. For users of embedded OS / RTOS they don't have to use assembly code. However, for people that actually create embedded OS / RTOS, they do need to do a bit of assembly code for SVC handler and context switching.
The phase completely programmable in C is somewhat a marketing statement. Many development tools still use assembly code for the startup codes (it is possible to do it in C too). And there are certainly some cases where I think should use assembly (e.g. context switching in OS, or any code that directly manipulate stack like some of the fault exception handlers that need to extract exception stack frames). However, for most general MCU applications, it is true that the software developers don't need to write their code in assembly languages.
regards,
Joseph
I would like to offer my view on this.
I see it as the ARM Cortex-M is one of the only processors / microcontrollers that offer a complete C-interface, exactly because it allows you to write Interrupt Service Routines as normal subroutines. -There is no difference at all.
The special interrupt handling (eg. clearing the pending bits) can be done using standard write operations.
One could say that the Cortex-M brings microcontrollers closer to C, but I would put it this way:The Cortex-M brings C closer to assembly level (eg. as enhancement).
So it's not really a matter of if it's easier to program the Cortex-M in C compared to assembly langugage (that would probably be true for most microcontrollers / microprocessors anyway; unless they have very little program space).
Rather, I would say that it's about making C programming easier on the Cortex-M than it has ever been before.
(Note: programming in assembly language is also easier on the Cortex-M than on earlier ARM based architectures; especially when speaking about interrupts and context-switching).
One thing I think that the CMSIS library should probably implement, would be __ROL() and __ROR().
I'm going a bit off-topic here: It's annoying that C still does not have a standardized function for this ... How many years is it since C was invented ? -And we had ROL and ROR instructions since before the 80's, but it's still not present in C++, ObjC or ObjC++.
Thanks for the useful examples and information .
One question: the stack array is declared as "char". I think C compiler could allocated this in non-word aligned addresses. And for AAPCS requirement you might possibly want to make the stack aligned to double word boundaries. So would it be better to declare it as a double word array?
uint64_t stack[64]; // 512 bytes
or use some other attribute to force double word data alignment?
(I didn't have enough coffee today so I might have miss something )
Hi all,
Thank you very much for the replies. Indeed helpful information and sorry for delayed reply. I
If I can summarize this, these are the points why it is easier to code entirely in C for the ARM Cortex-M:
1. CMSIS offers the wrappers.
2. The SP is initialized by hardware on reset.
3. The interrupt subroutines can be written exactly like normal subroutines. The special interrupt handling (eg. clearing the pending bits) can be done using standard write operations.
Some parts of code like for e.g. the SVC handler would still need some assembly code. Some details are here as well - ARM Information Center
And above all, as Joseph says, this is somewhat a Marketing statement!@
Thank you jensbauer for your additional input. I have edited my earlier post to include your point. Besides, your off-topic point is also valid
You're correct, I forgot the alignment attribute in this "example" (e.g. alignas), though it's probably better not to take the example too serious anyway since, as I mentioned it's rather brittle. Code that requires optimization to be enabled and crashes if it's not would typically be frowned upon I think.
I'm no fan of the mandatory dword stack alignment. When targeting cortex-m3 processors where dword alignment has absolutely no benefit I don't want to waste stack space. If I ever move my code to a processor that does care, I'll recompile. The alignment assumption is, if I'm not mistaken, indicated in an attribute in the compiled object so if someone accidently tries to link together code with incompatible assumptions it should give a linker error.
Especially since it would have been a lot easier to add it as a primitive than going through the effort of recognizing an expression which implements a rol/ror using other bitwise ops (which is what all decent C compilers do).
Thank you for this question. I never expected that i would much response to this one .
To all participants , it's great work
In theory you can sometimes get away with initializing SP with something like... (yes, those are C++11 style attributes )
extern void init(); extern int main(); [[ noreturn ]] extern void exit(int); [[ noreturn ]] [[ gnu::noinline ]] void _start2() { init(); exit( main() ); } char stack[512] alignas(8); [[ noreturn ]] void _start() { asm( "mov sp, %0" :: "r" (stack + sizeof(stack)) ); _start2(); }
relying on the tail-call being optimized, and clang (git) indeed produces working code from this depending on options, with -O1 -fomit-frame-pointer -fno-exceptions seemingly being the magic ingredients required. But gcc (4.9.1) keeps insisting on "saving" r3 and lr on the stack, even though it knows the function is noreturn and in fact doesn't generate any corresponding code to "restore" them again. Putting [[gnu::naked]] on _start() convinces gcc to omit this prologue-of-doom, resulting in functioning code, but this attribute makes clang very unhappy.
Gcc's stubbornness appears to be a side-effect of excessive precaution around a volatile asm statement, since fetching SP in a similar way does work, e.g. to provide this very useful "add integer" system call:
struct HandlerFrame { u32 r0, r1, r2, r3, r12, lr; u32 pc, xpsr; }; [[ gnu::noinline ]] void do_sys_add( HandlerFrame *frame ) { frame->r0 += frame->r1; } void syscall_handler() { HandlerFrame *frame; asm( "mov %0, sp" : "=r" (frame) ); do_sys_add( frame ); }
which needs only -O1 for clang and -O2 for gcc to compile correctly.
Of course, this hackery is brittle as hell and it's not exactly plain C/C++ anymore either; writing some glue in assembly is much saner. Writing ARM assembly is quite pleasant anyhow, we're not talking about x86 here (*shudder*).
Update: edited to add alignment attribute to stack, which was missing as Joseph Yiu pointed out in the comments.