Several comments:
1) Absolutely not assembly unless performance or optimization constraints warrant it. The following metrics go through the roof with assembly:
- time to code it
- time to debug it
- time to test it
- time to document it
- time to figure out (1 year later) what it was you were doing when you coded it
- chances of making a mistake
2) My preference would be C++ rather than C for its namespace encapsulation & its facilitation of compile-time object-oriented practices. C has too many opportunities for global variables and namespace collisions. (Real-time Java would be nice but from what I understand its requirements are still pretty high)
Or rather a subset of C++: Exclude exceptions, virtual functions, run-time type identification, also dynamic memory allocation in most cases -- basically anything that's left unspecified at compile time, as it will usually require a lot of extra resources during runtime. That's the "bloat" of C++.
I have used both TI's and IAR's compilers for C++, for the TMS320 and MSP430 microcontrollers (respectively) and with proper optimization settings, they do a fantastic job of reducing the overhead you might expect from C++. (Especially if you help it out by judicious use of the inline
keyword)
I have even used templates for some of their compile-time benefits which promote good code reuse: e.g. writing a single source code file to handle 8-bit, 16-bit, and 32-bit CRCs; and compile-time polymorphism to allow you to specify the usual behavior of a class, and then reuse that but override some of its functions. Again, the TI compiler had an extremely low overhead with appropriate optimization settings.
I have been looking for a C++ compiler for the Microchip PICs; the only company I've found that produces one is IAR. ($$$ has been an obstacle but I hope to buy a copy sometime) The Microchip C18/C30 compilers are pretty good but they're C, not C++.
3) A specific caveat about compiler optimization: it can/will make debugging very difficult; often it's impossible to single-step through optimized C/C++ code and your watch windows may show variables that have no correlation with what you think they should contain with unoptimized code. (A good debugger would warn you that a particular variable has been optimized out of existence or into a register rather than a memory location. Many debuggers do not. >:(
Also a good compiler would let you pick/choose optimization at the function level through #pragmas. The ones I've used only let you specify optimization at the file level.
4) Interfacing C code to assembly: This is usually difficult. The easiest way is to make a stub function that has the signature you want e.g. uint16_t foo(uint16_t a, uint32_t b) {return 0; }
, where uint16_t
= unsigned short, we usually make the # of bits explicit. Then compile it and edit the assembly it produces (just make sure to leave the begin/exit parts of the code) and be careful not to clobber any registers without restoring them after you are done.
Inline assembly usually can have problems unless you are doing something very simple like enabling/disabling interrupts.
The approach I like best is compiler intrinsics / "extended ASM" syntax. Microchip's C compiler is based on the GNU C compiler and it has "extended ASM" which lets you code bits of inline assembly but you can give it lots of hints to tell it which registers/variables you are referencing and it will handle all the saving/restoring of registers to make sure your assembly code "plays nice" with C. TI's compiler for the TMS320 DSP doesn't support these; it does have a limited set of intrinsics which have some use.
I've used assembly to optimize some control loop code that got executed frequently, or to calculate sin(), cos(), and arctan(). But otherwise I'd stay away from assembly and stick with a high-level language.