0
\$\begingroup\$

I was coding for Atmel ATmega 32 chips. Now I have shifted to the ARM Cortex-M family.

Is there any difference in coding between AVR and ARM, or does it depend on the compiler? How I would learn programming for ARM MCUs?

\$\endgroup\$

closed as too broad by Rev1.0, PeterJ, Dmitry Grigoryev, Voltage Spike, Dave Tweed Sep 30 '17 at 11:55

Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • \$\begingroup\$ Your question is very broad, almost too broad even. I've tried to answer it best I could, but it'd really help if you could make it more specific. What do you want to accomplish? What have you tried and which problems did you encounter along the way? \$\endgroup\$ – Mels Sep 27 '17 at 11:46
  • \$\begingroup\$ Yes, certain aspects will depend on both microcontroller family and compiler. One might wonder: "What aspects will depend how much?" There's no easy answer to this question, but I can share my experience. When I have to switch to a new microcontroller family or a compiler, it takes me a month of full-time work to get familiar with the new stuff enough to be able to write production code. \$\endgroup\$ – Nick Alexeev Oct 2 '17 at 5:38
3
\$\begingroup\$

There is some similarity (obviously ANSI C is pretty much ANSI C) but the differences in hardware will result in a great increase in complexity in most cases when you use an ARM or other 32-bit processor. It's not that you can't do simple programs that directly access hardware on an ARM but usually there are hardware abstraction layers and middleware and maybe an RTOS to contend with in order to get a program running that was the reason you wanted to use an ARM in the first place.

A few things are probably a bit simpler- but there are also memory alignment and memory barrier issues to deal with. And threading issues if you use an RTOS (which is pretty much what you deal with anyway when you use interrupts but with different terminology). The bus structure is far more complex. You'll likely want to consider using DMA or other complex peripherals.

You'll have to adapt to the naming conventions used by the middleware provider if you want to use their USB stack, Ethernet stack etc. It is said that

"There are only two hard things in Computer Science: cache invalidation, naming things, and off-by-one errors"


Here is an ASF (Atmel Software Framework) example of writing to a port (blinky):

in main.c:

ioport_set_pin_level(LED1_GPIO, IOPORT_PIN_LEVEL_HIGH);

in ioport.h

static inline void ioport_set_pin_level(ioport_pin_t pin, bool level)
{
    arch_ioport_set_pin_level(pin, level);
}

in ioport_pio.h

__always_inline static void arch_ioport_set_pin_level(ioport_pin_t pin,
        bool level)
{
    Pio *base = arch_ioport_pin_to_base(pin);

    if (level) {
        base->PIO_SODR = arch_ioport_pin_to_mask(pin);
    } else {
        base->PIO_CODR = arch_ioport_pin_to_mask(pin);
    }
}

Also in ioport.h:

/** \brief IOPORT levels */
enum ioport_value {
    IOPORT_PIN_LEVEL_LOW,  /*!< IOPORT pin value low */
    IOPORT_PIN_LEVEL_HIGH, /*!< IOPORT pin value high */
};
\$\endgroup\$
  • \$\begingroup\$ So the syntax of the embedded c is the same but the complexity of the program is not according to the hardware differences RIGHT? \$\endgroup\$ – Ahmed Essam Sep 27 '17 at 14:39
  • 1
    \$\begingroup\$ I would say it's a consequence of the increased hardware capability. You'd have to be a bit unbalanced to write a flat program that handles Ethernet, for example. Also some of the tricks used to make CPUs run faster than tens of MHz (caches and out-of-order execution) present challenges. I'll edit in above an example of writing to a port. \$\endgroup\$ – Spehro Pefhany Sep 27 '17 at 14:58
  • 1
    \$\begingroup\$ I would note that when writing code for ARM on bare metal, understanding the CMSIS interface details is necessary and quite a task. \$\endgroup\$ – Peter Smith Sep 27 '17 at 15:03
  • 1
    \$\begingroup\$ ANSI C may be ANSI C, but that's ANSI C89. We've since moved to C90, C99, and C11. And those are definitely different. Is that relevant to MCU's? struct Foo MyFoo = { .x = 7, .y = 8 }; is perfectly reasonable even on small embedded systems, but it's not ANSI C89. \$\endgroup\$ – MSalters Sep 27 '17 at 15:16
  • \$\begingroup\$ @PeterSmith Yes quite a task. There is nothing stopping you from ignoring the abstraction layer and dealing with the hardware directly. The hardware addresses and what the bits do are documented. \$\endgroup\$ – Spehro Pefhany Sep 27 '17 at 15:16
4
\$\begingroup\$

In short: yes and no.

The C language will be mostly the same regardless of architecture, although there will be some variations depending on the specific compiler that you use.

However, using a different hardware architecture and its corresponding toolchain will inherently give you vastly different ways to use the hardware's registers, interrupt vectors, etcetera. Nothing a few tutorials won't help you with, though. Good luck!

\$\endgroup\$
2
\$\begingroup\$

An important point to watch out for is that different compilers will attempt to "optimize" code with differing levels of aggressiveness, in ways that would be allowable under the C Standard but would make the compilers unsuitable for processing certain kinds of code unless it includes compiler-specific directives which would not have been necessary when using other compilers that "optimize" less aggressively.

For example, given something like:

// Simple polling function to acquire some data and store it, using the
// return value to indicate success.  Note that there is no reason why
// this function should need to make "dest" volatile.

extern uint32_t volatile data_ready;
extern uint16_t volatile data_source;
int get_data(uint16_t *dest){
  if (!data_ready) return 0;
  *dest=data_source;
  return 1;
}

// Set up and trigger outside interrupt, DMA, or other such process
// to output data and wait for that process to complete.

extern uint32_t volatile out_length;
extern uint16_t volatile * volatile out_ptr;
void write_data(uint16_t *buff, uint32_t len)
{
  out_ptr = buff;
  out_length = len;
  do {} while(out_length);  
}

// Sample code using the above.  Note that if "buff" were volatile,
// its address could not be passed to "get_data" unless get_data took
// a volatile pointer, and making "buff" volatile would compel test()
// to do a couple of unnecessary extra stores to it.

uint16_t buff;
void test(void)
{
  while (!get_data(&buff));
  buff+=1;
  write_data(&buff, 1);
  while (!get_data(&buff));
  buff+=1;
  write_data(&buff, 1);
}

ARM gcc 6.3.0 will ignore the value stored into "buff" by the first call to get_data if invoked at -O2 or higher, since it sees that it will get overwritten by the second call. It will do this even if the -fno-strict-aliasing option is used. To make code compatible with gcc, it would be necessary to either make buff volatile (which would in turn require that any functions that accept pointers to it qualify those pointers as volatile), use optimization level 0 or 1, or insert a __asm directive to force the compiler to recognize that stores to volatile locations can have side-effects on other objects.

The authors of gcc take the attitude that the code as written is broken since the Standard does not require that all compilers--even those not intended for use in embedded or systems programming, or targeting systems where no physical means of causing side-effects would exist--treat volatile accesses as having potential side-effects on other objects. I personally think it's the gcc authors' attitude that's broken, but I'll leave such judgements up to the reader.

PS--If the functions were written as macros:

#define get_data(dest) (data_ready ? 0 : ((*dest=data_source),1))
#define write_data(buff,len) do {\
  out_ptr=buff; out_length=len; do {} while(out_length); \
} while(0)

gcc would break the code even at optimization level 1.

\$\endgroup\$
  • \$\begingroup\$ I'm not sure I'm following your example here. Reads from and writes to volatile are observable and may not be eliminated or reordered relative to each other. \$\endgroup\$ – MSalters Sep 28 '17 at 9:45
  • \$\begingroup\$ @MSalters: On some platforms, accesses to certain storage locations may trigger actions capable of affecting any object anywhere. On other platforms, that isn't physically possible. The authors of the Standard specified one means by which code can tell a compiler that accessing an object will have effects it doesn't know about, but left the question of what effects compilers should allow for up to the implementers' judgment. The problem is some compiler writers' idea of what they should allow for is out of sync with how the underlying platforms work. \$\endgroup\$ – supercat Sep 28 '17 at 14:35
  • \$\begingroup\$ @MSalters: The code above requires that one or more accesses be performed to buff in arbitrary order (allowing for consolidation), followed by accesses to out_ptr and out_length, followed by one or more accesses to buff in arbitrary order, followed by more accesses to out_ptr and out_length. Generating correct code requires that the accesses to buff be ordered with respect to out_length. Generating optimal code requires that they be unordered with respect to each other. I would suggest that an implementer who is exercising good judgment should make it possible... \$\endgroup\$ – supercat Sep 28 '17 at 14:39
  • \$\begingroup\$ " one or more accesses be performed to buff in arbitrary order (allowing for consolidation), followed by accesses to out_ptr" - that explicitly relies on sequencing of a non-volatile access and a volatile access. I really can't find fault with GCC here. \$\endgroup\$ – MSalters Sep 28 '17 at 14:43
  • \$\begingroup\$ ...to produce code which is efficient and correct without requiring non-standard syntax. \$\endgroup\$ – supercat Sep 28 '17 at 14:43
0
\$\begingroup\$

Proper C code should be portable if you don’t depend on machine- or compiler specifics. For example if you want an integer of a certain size you should use the stdint.h header and don’t trust in e.g. int being 16 or 32 bit. Also don’t trust in the alignment staying the same.

Of course the peripherals, memory addresses etc. between different CPUs can differ greatly.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.