C++ bloat?

Go To Last Post
40 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am sceptical of reports of C++ generating more machine code than C.  My simple test shows only a little increase in code size.

 
I found a simple C Xmega program that blinks a LED using delay_ms.  I made a copy and replaced the three C  style port I/O functions with C++ functions in the copy.  The resulting C++ program was 8 bytes bigger than the C program.

 

8 bytes out of 600 is trivial to me.  However It does seem that the system is reserving half of the 800 for interrupt vectors or something for my Xmega 128a4u.  So that would make it 8 out of 300 bytes.  Still nothing compared to the skin rashes and headaches I would get just thinking of using C.  :)

 

If we could get a bit more complex program to compare, that would be good.  If someone could find a C blinker program for the Xmega that uses RTC instead of delay_ms, that would be great.  I could easily add RTC to the c++ program.

 

One reason for the bigger C++ program could be that the C++ compiler doesn't produce as good AVR code.  I took a quick look at the .lss files and I noticed one place where the C compiler used rcall and rjmp and the C++ compiler used call and jmp.  I don't know much about the AVR instructions, but it seems the call could be replaced with rcall.  That would save 2 bytes.  Maybe the jmp could be replaces with rjmp also, but I don't know how far the rjmp can jump.

 

  C compiler
 214:    02 d0           rcall    .+4          ; 0x21a <main>
 216:    1b c0           rjmp    .+54         ; 0x24e <_exit>

 

  CPP compiler
 214:    0e 94 10 01     call    0x220    ; 0x220 <main>
 218:    0c 94 2b 01     jmp    0x256    ; 0x256 <_exit>

 

I'm using the C++ compiler that came with the last Atmel Studio 7 Version 7.0.2397

gcc version 4.6.2

 

 

I am attaching main.c and main.cpp. Also the 2 studio projects.

 

 

 

 

Attachment(s): 

Last Edited: Sun. Nov 29, 2020 - 01:17 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Is that really what you want? I know little C++ but does not "new" put the memory on the heap, and then when you spin it in a loop, that would be extra bad.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

steve17 wrote:

I am sceptical of reports of C++ generating more machine code than C...If we could get a bit more complex program to compare, that would be good.  If someone could find a C blinker program...

 

I doubt you will get any meaningful results. The very nature of a C++ program is that it (should) be written differently to a C program. A conversion from C to C++ is a pointless exercise; the whole program should be re-written to use the features of C++, and by that point no comparison can be made.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You are making it more complicated than it needs to be. If I was required to write this in my application code I think I would skip C++ -

    (new Port_c_device)->Set_pins_direction_out(Port_device::Pin7);
    (new Port_c_device)->Set_out_pins_high(Port_device::Pin7);

No offense meant, but that is what we are trying to escape. How are you going to get a friendly name attached to that? There lies a problem, and the solution to get a name attached to pin c7 will most likely end up similar to what one would do in C.

 

Wouldn't you rather have something like this-

 

    Pin<A0> sw { LOWISON, INPUT, PULLUPON }; //options in any order
    Pin<B2> led{ OUTPUT, LOWISON };
   
    while( sw.isOff() ); //press sw to start

 

    while(true){
        led.toggle();
        waitms( 500 );
    }

 

You get to specify the name of the pin and set its properties all in one line, and the resulting code will be as minimal as anything. Now you have a name you can use that has a set of functions 'attached' to the name. Yes, a little template learning is required but is worth the time to at least figure it out to some degree. Template use for peripherals probably becomes less important when you have an mcu that is more capable, but even with a more powerful mcu there is little downside to using templates for peripherals.

 

Here is a simple C++ style (or outline) I put up on github a few days ago, just because I wanted it written down somewhere-

https://github.com/cv007/C-PlusP...

 

It starts with a mega328p (I don't even use a mega328, but makes for a simple example), and then has a few more advanced for a mega4809. I could add more, but the rest is mostly just the same. I use this style on any mcu and any peripheral and they all end up looking basically the same and work quite well.

 

I have refined this 'style' for a while now, and seems to work well (avr0/1, nRF52, a little stm32, etc.). For the avr0/1 I have most peripherals done (each chapter in the datasheet), (redone again, now even better), and for things like the nRF52 I just 'take over' a peripheral as wanted/needed (the rest ends up with a C++ layer on top of the manufacturer provided things). All files are header only, a single file per peripheral, and to use it- just include it. 

 

What you get is a nice way to create a peripheral struct that is specific in its properties at compile time (the things that separate it from the other peripheral instances), you get a set of enums that are specific to the peripheral (and enum types are great for function arguments as they naturally 'assert'),  the peripheral register layout is contained within if wanted, and the functions end up short and simple because the register access is simple. A peripheral contained in a single header, which never needs adding to the project source list.

 

As already mentioned, it doesn't take long before any comparison to a C equivalent becomes hard to do. For the most part, you do equivalent things you are going to get equivalent results unless you code your C++ like you are programming a pc. C++ gives you an easy way to do things that are awkward to do in C, so you soon get to a point where you can imagine what it takes to do the equivalent in C, but you certainly are not going to take the time to prove the point to yourself. You can already see there is no way you are going to give up C++ even if the resulting binary size is a little bigger.

 

What does happen, is you get a system that is easy to use so you use it (size increases)- have a Buffer class and a Usart class that can optionally take in a Buffer? little reason not to use it as only requires a line of code to create the Buffer and you no longer block on any uart output. Nice buffer in place, so why not send debug info out the uart at high speed (no major effect on running mcu), turning on/off the debug output as needed (single line of code). Rtc and Time classes in place? Well, since we now have a system time in unix seconds and fractional time in 0-999ms, may as well output the system time in the debug output. Hey, we have a system time with resolution down to a ms, so now that can be used for both tasks and long term events in addition to providing clock time- one universal time for everything outside any precision/hardware required timing needs. Your binary sizes increase because you are simply doing more, you are doing more because it is easier to do so.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Brian Fairchild wrote:
I doubt you will get any meaningful results.

I would tend to concur.

 

Also, trivial programs don't (generally) give a true  representation - using printf in simple "Hello World" being a classic example.

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 2

The only people who claim C++ is "bloaty" generally seem to be those who don't actually use it! ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

or the ones who try to do "bloaty" things - which would probably take even more to do in 'C' ...

 

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

clawson wrote:
The only people who claim C++ is "bloaty" generally seem to be those who don't actually use it!

OK, let me see if I can make a corollary:

 

"I'm reading many results that the ZeXeY automobile Model CoBiA has a lot of problems, so I'm not going to buy one to replace my trusty car."

 

"Hmmmph!  How can you say that when you haven't spent (wasted?) the money on one?!?"

 

And that is fundamentally different than your point about putting in the effort and cost to learn?

 

[I'll do some digging, but isn't there a more-or-less seminal thread about gcc/C++/AVR that says something like "use C++ on the AVR without problems if you don't do these three things" ? ]

 

[edit]  Earliest "summary" that I found:

 

[edit]  Here is a pretty  good discussion with many of the back-and-forths that OP might digest before reinventing it all.

https://www.avrfreaks.net/forum/...

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Sun. Nov 29, 2020 - 08:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The call vs. rcall thing is not C/C++-specific.

My understanding is that at least some of

those optimizations are done by the linker.

OP was testing the linker.

 

Also, new is C++'s new improved malloc.

Do not use new if you would ot use malloc.

 

All that said, C++ is bigger than C.

More of compiler-writers' time has to be

spent on handling the language itself.

That leaves less time for optimizations

that might be handled at the front end.

 

I've looked at the result of -O0 with avr-gcc.

'Tis bloated to illegibility.

I expect some improvements from -O0 to -O1 are handled at the front end:

special case code for obvious code.

My guess is that C++ has more special cases.

 

Also, avr-gcc suffers from being low on GNU's

totem pole and from having 16-bit ints.

GNU's instructions to contributors explicitly state that they

need not consider the possibility of ints smaller than 32 bits.

That is why gcc's generic software floating point does not work for avr-gcc.

Improvements for multi-core 64-bit

processors can make AVR code worse.

 

The guys that brought us the avr- version of gcc did lot of good work.

 

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ron_sutherland wrote:

Is that really what you want? I know little C++ but does not "new" put the memory on the heap, and then when you spin it in a loop, that would be extra bad.

new attempts to create an object of the specified type and returns a pointer to the object.  It calls the new operator function to do the work, which you supply.  Actually when your C++ compiler was installed on a PC, it also installed a default function when you weren't looking.  Here is a genuine one installed 20 years ago when life was simpler and not filled with asserts and stuff.  Notice it calls malloc().

 

/***
*new.cxx - defines C++ new routine
*
*       Copyright (c) 1990-2001, Microsoft Corporation.  All rights reserved.
*
*Purpose:
*       Defines C++ new routine.
*
*******************************************************************************/

#include <malloc.h>
#include <new.h>
#include <stddef.h>

void * operator new( size_t cb )
{
        void *res = malloc( cb);
        return res;
}

 

 

When creating a class, you can supply the new operator function which overrides the default one.  It can do what it wants as long as it returns a pointer to the object. If you look at the Port_c_device class, you will find this:

 

void* operator new(size_t objsize)   {
      return (void*)_SFR_ADDR(PORTC);            // assign actual memory location of the registers
      }

 

I returns the address of Port C.  All class member functions that are written in the class are subject to inlining and always are, in my experience.  So this is just a way to tell the compiler what the address of Port_c_device is.

 

I admit that newing the same class multiple times is logically goofy.  Logically I should do it once and save the pointer.  When I found I got the same program size either way, I went for the easier way.  smiley

 

 

 

 

Last Edited: Sun. Nov 29, 2020 - 06:05 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steve17 wrote:
When creating a class, you can supply the new operator function which overrides the default one. 

 

Hmm. Overloading. So is it a virtue with C++ to have many different ways to do a thing?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well it can sure obfuscate code! Most people have an idea of what new does until someone perverts it!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Overloading. So is it a virtue with C++ to have many different ways to do a thing?

It is a virtue to be able to use the same way to do something for many types of things...

 

I don't think anyone doubts that you can write C++ code that isn't bloated.  It's just that you can get ... surprised when something blows up a lot more than you expected.

For example, I don't see a way to make virtual functions of a class without defeating link-time garbage collection of unused functions.  As a result, Arduino sketches that do anything at all with Serial also end up including several rarely-used bits of code for peek(), availableForWrite(), and etc.  Most of the boards in question have small implementations and enough memory that this isn't much of a concern.  But it could be...

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steve17 wrote:

Here is a genuine one installed 20 years ago when life was simpler and not filled with asserts and stuff.  Notice it calls malloc().

 

Well, if you look at the Arduino AVR source code, new is basically just a wrapper for malloc. So if someone abuses new everywhere, don't go blaming the bloat on C++, it's malloc.

https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/new.cpp

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

El Tangas wrote:

Well, if you look at the Arduino AVR source code, new is basically just a wrapper for malloc.

People who put malloc on a microcontroller are highly likely to be clueless.  You might want to avoid Arduino source code.

 

When putting stuff on the heap, you should use something a lot simpler.  This is what I use.

 

 

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steve17 wrote:

People who put malloc on a microcontroller are highly likely to be clueless.  You might want to avoid Arduino source code.

 

Well, I think it's well known that Arduino libraries are not exactly the pinnacle of optimization, quite the opposite in fact.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:
I don't see a way to make virtual functions of a class without defeating link-time garbage collection of unused functions.
  For my micro OS, I just use one virtual function called Process_event().  All my tasks have a function of the same name.  They all inherit a small base class I call Task.  These Task classes have one virtual function of that name.  They chain themselves into a linked list that the Task_runner (I might call it Task_master to be non PC) searches.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ron_sutherland wrote:
Hmm. Overloading. So is it a virtue with C++ to have many different ways to do a thing?
How many ways are there to get the address of an I/O device class, or any memory at a fixed location?  Apparently some people use a template.  That seems like clumsy code to me.  Why not use new, which was designed for this?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steve17 wrote:

El Tangas wrote:

Well, if you look at the Arduino AVR source code, new is basically just a wrapper for malloc.

People who put malloc on a microcontroller are highly likely to be clueless.  You might want to avoid Arduino source code.

 

When putting stuff on the heap, you should use something a lot simpler.  This is what I use.

 

If you're talking about the 'classic' 2K parts then yes, I'd agree. But having moved my current projects to AVR-DA, I have more than enough memory (famous last words) and am no longer counting every byte. I currently have 12947 bytes of 16K free :)

 

That's not to say that one can blithely disregard the downsides of dynamic allocation, but it's less of an issue for me now and I can focus on more productive tasks.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ron_sutherland wrote:
Hmm. Overloading. So is it a virtue with C++ to have many different ways to do a thing?
Technically speaking, You can't overload the new operator.  It's the only C++ operator I know of (probably delete too) that has no built-in function.  There are reasons for that. 

 

malloc is not part of C++.  C++ can't assume that malloc is available.  There are various places besides the heap you might want to use.  Maybe shared memory or machine registers etc..

 

So you must supply your own new operator function.  Of course when C++ is installed on a PC, the installation includes malloc and includes the default new operator function, so you can remain fat, dumb and happy.  smiley

 

I found, and attached, the default new operator function that was installed recently on my Win 10.  If you scan this beauty, you will see it. 

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I should have attached the .txt version for people that can't use .rtf.

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

malloc is part of C++.

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Apparently some people use a template.  That seems like clumsy code to me.  Why not use new, which was designed for this?

I'm not quite sure why the Rube Goldberg contraption is used to get what you want, but its your mcu.

 

Here is the simplest example from my 'tutorial' on github I linked to earlier-

 

https://godbolt.org/z/6cq3h5

 

Put up something on godbolt.org that does the same- set any pin on a mega328p to an output, and turn it on/off/toggle. I would like to see what you come up with, and if it turns out to be something better, I'll change my mind (always looking for something better). The only 'rule' will be that you will have to use a name like 'led' in the main function (if I have to remember that the led is on pin c7, no good).

 

Hmm. Overloading. So is it a virtue with C++ to have many different ways to do a thing?

Overloading is a major feature, and gets used often. Without it, no templates for example.

 

A simple example, that shows a different way to do print things other than using virtual functions like arduino-

 

https://godbolt.org/z/r7eMrK

 

It appears the Print function is overloaded twice (1 being an empty function so you can easily enable/disable printing, such as debug output), but since its a function template it is overloaded for as many devices that end up using it. So to 'print' from a device of any kind (using the stdio things), it just needs a write function with a specific function type that matches what FILE.put uses. They all end up using fprintf (a single function from library), so basically each resulting version mostly deals with setting up the call to fprintf just like any other C app.

 

Another simple example using the example code linked above-

    SA  high        ()  { reg.OUT = 1; }  //SA=static auto
    SA  low         ()  { reg.OUT = 0; } 
    SA  on          ()  { if(Inv_) low(); else high(); } //Inv_ is template argument for HIGHISON/LOWISON 
    SA  off         ()  { if(Inv_) high(); else low(); } 

 

for the on() I normally also have another -

    SA  on          (bool tf)  { if(tf) on(); else off(); }

 

which then replaces code like this-

    if( isSomething() ) led.on(); else led.off();

 

into something nicer to use when needed-

    led.on( isSomething() );

 

which results in the same code produced, but you no longer have to do an if/else every time you need a pin set to the result of some decision.

 

Default arguments can also be done, which could replace the overloading in this case-

    SA  on          (bool tf = true)  { if(Inv_==tf) low(); else high(); }

 

 

Another example-

https://godbolt.org/z/vojbhz

There are several on's in there, and the last one also has a default argument. So you end up with a version which simply does as its told, another which also can set the inputs also, and a final one which can also setup irq's. The one's with more parameters can use the 'smaller' versions so no need to rewrite everything. The added overload options means you can do something in a single command without having to go through all the separate steps yourself. If its not used, no big deal, compiler knows its not used.

 

 

Back to the original question-

 

I have a simple bootloader for any avr0/1, written in C and compiles to 380 bytes (.vectors section commented out of linker script to remove vectors). I translated it to C++ using the existing C++ peripheral drivers I have. I left out the autobaud and some syncing bytes in the C++ version, and the compiled size is 576 bytes and will be in the 600's if I would make it complete (no global constructors needed/used, so no startup code for that). So it is 'bloaty', but the reason it is larger is because the C version was written from scratch with the knowledge of starting in a reset state which is a big advantage. The C++ drivers I have make no assumptions about the current state of registers so it ends up doing things in a more reliable way that is not necessary in this case. One example would be setting up portmux to use an alternate pin- the C version knows the state of the portmux register, the C++ version does not, so ends up clearing/setting the needed bits. All those type of things add up, and I would imagine if I had used existing C drivers the sizes would be more similar. I also imagine if I had written the C++ version from scratch, it would also be similar, but its a little bit pointless to ignore existing drivers where all the hard work has already been done, just to save a few hundred bytes.

 

Last Edited: Mon. Nov 30, 2020 - 08:34 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:

(if I have to remember that the led is on pin c7, no good).

 

Yes, I agree.  I don't know why I did it like that.  I guess I just wanted to make substitutions of the 3 lines, to make it simple.

 

In my code I'd do something like this.

#define LED_PORT  Port_c_device

#define LED_PIN    Port_device::Pin7

 

The device classes contain functions that do whatever you want to do to the device. All you need to do is find the function that does it.  This means the user doesn't need to know anything about the registers and the pins in those registers.  He doesn't need to know if there are bit flips that have to be done in a certain order, or if CCP has to be disabled, etc.. 

 

Even C programmers might find these classes useful. At the least, they can look at these classes to see what needs to be done.  If the project has a C++ compiler, he can even call these functions by using an intermediate function that knows how to call a class member function

 

These device classes only need to be written once.  Atmel should have written them years ago.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steve17 wrote:

curtvm wrote:

 

(if I have to remember that the led is on pin c7, no good).

 

Yes, I agree.  I don't know why I did it like that.  I guess I just wanted to make substitutions of the 3 lines, to make it simple.

 

Indeed, it's not very useful to just use C++ to address individual pins, since C can also achieve this. For example, this blinky for an Arduino nano board (I know, I could use toggle but this is just to keep with the original example)

https://godbolt.org/z/f4K7cj

 

To really highlight what C++ can bring to the table, you have to go for something like curtvm showed.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In my code I'd do something like this.

#define LED_PORT  Port_c_device

#define LED_PIN    Port_device::Pin7

You would rather create a set of defines just like C?

 

    (new LED_PORT)->Set_pins_direction_out(LED_PIN);
    (new LED_PORT)->Set_out_pins_high(LED_PIN);

 

Why not just stick to C then?

 

    LED_PORT->DIRSET = LED_PINbm;

    LED_PORT->OUTSET = LED_PINbm;

 

Rather, how about just doing something C++ like-

 

    Pin<B2> led{ OUTPUT };

    led.on();

 

No defines, one line, we get a name to use, and have a set of functions attached to the name. Need to change the pin? change the B2 to something else, done.

 

I probably sound harsh, but am just trying to point out that there is a better way (I think so, anyway). I have already posted enough links to examples so will try to refrain from showing any more. My last post has 'another example' which is an Ac peripheral in a mega328p. All peripherals end up looking like that- a single header file for each peripheral with no need to touch any peripheral registers when writing app code, no need for an mcu header since its easy to create a register layout struct for register access where its needed (in the peripheral struct), and all the needed enums also live in the peripheral struct. One file and we have everything we need to run a peripheral, and only need to include it to use it. For the most part the functions inside stay pretty simple and just take the drudgery out of doing the common-

 

    //a Buffer class of 64 u8's (with all the functions you will need- append,remove,+ a dozen more)

    Buffer<u8, 64> txbuf;

    Usart0alt uart; //usart0, alternate pins

    //turn on tx (+set portmux, + set pin to output), optionally provide a buffer(will then use tx irq),

    //230400 baud (run time calculated from current cpu speed, can also do compile time calculation if wanted)

    uart.txOn( txbuf, 230400 );

    //now can Print using printf style

    Print( uart, "Hello World\n" );

 

So something like the above to setup a way to print from a uart becomes easy, and with 3 lines of code we are all set. You also have easy access to the source of anything- want to see what txOn() does? you will be taken directly to the source in Usart.hpp, where the function will be simple and everything you want to see will be in that file with no need to be bouncing around headers until you finally reach what you were looking for.

 

These device classes only need to be written once.  Atmel should have written them years ago.

It may be a good thing these manufacturers do not touch C++. Somehow I think they would turn it into something closer to awful than good.

 

 

As long as I'm filling up the avrfreaks server storage with my comments, I will add that I have projects in C++ on github (which I use mainly for backup/storage) that I would probably cringe a little if I go and take a closer look at them again. I think what I do now in C++ is much better, but I always hold out hope for better than what I have now and may end up looking back at the 'now' and cringe about it in the future.

Last Edited: Tue. Dec 1, 2020 - 01:05 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It may be a good thing these manufacturers do not touch C++. Somehow I think they would turn it into something closer to awful than good.

Sigh.  An incredibly sad, and probably very accurate, point.  Look at any vendor's C library :-(

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
   These device classes only need to be written once.  Atmel should have written them years ago.

It may be a good thing these manufacturers do not touch C++. Somehow I think they would turn it into something closer to awful than good.

Whar?  Since when is giving us a class touching C++?

 

I've written a lot of classes but that doesn't do anything to C++.  Just because they give us a class, doesn't mean we have to use it.

They give us the io....h file that contains the registers, which is a freaking mess because C doesn't understand classes but that doesn't change C, it just gives me a rash when I look at it.  Actually they give the same set of registers, with different names,  for each port.

 

They should give us the I/O device class that contains the registers and the functions that operate on them.  One port class works for all ports, only the addresses are different. 

 

They do supply a struct containing the registers, but most people don't use it because it's too clumsy to use.  The functions that access the registers have to also specify the instance of the struct that contains the registers.  I think of it as the functions are outside the struct looking in, whereas the C++ functions are inside with the data.

 

In C++, the user can tell the compiler that the functions that access the data in the struct are members of the struct.  Now those functions only need to specify the data (registers in this case).  One way to do that is write the functions inside the struct.

 

The actual functions that result are probably the same as the C functions, but it's a hell of a lot easier to write the code and to understand the code.  Of course we usually call these things classes instead of structs. 

 

In fact I've just explained the main difference between C and C++.  If you add the new operator and inheritance,  that's about all I know and need to know about C++.  When Stroustrup put classes from the Simula67 language into C, he called the result "C with classes".  Someone else called it C++.

 

 So now we have the data, in this case registers, and the functions that operate on them in one neat package.  We can put other things in the class too, like the pin names like Pin7 etc.  C programmers could do something like that, but where would these names be put?  In a simple program they could put them anywhere but in a big program, well I call C programs free range chicken code. 

 

I attached my project that contains the Port_device class in my first post, but I will attach it here too.  Atmel could give us classes for all the devices, but maybe they never heard of classes.  I fellow in Oslo invented them around 1965 but I guess news travels slowly in a country where travel is sleighs pulled by reindeer.  smiley

Attachment(s): 

Last Edited: Wed. Dec 2, 2020 - 01:31 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I infer from the data members of Port_device,

that Xmega pin port registers are grouped by port.

IIRC that is not the case with all megas.

 

Also IIRC microchip-supplied headers supply a struct containing all the IO registers.

That should make it fairly easy to do pretty much anything one wants,

even templates.

offsetof always produces an integer valid for anything for which an integer constant can be used,

including a template parameter and an enum value.

Even though it can be evaluated at compile time,

(unsigned)&PORTB is not even regarded as a constant expression of any kind.

 

BTW is Register unsigned char?

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>Whar?  Since when is giving us a class touching C++?

>I've written a lot of classes but that doesn't do anything to C++.

 

As soon as you are required to use a C++ compiler, you are touching C++. I take it your code compiles in C? of course not.

 

 

 

> If you add the new operator and inheritance,  that's about all I know and need to know about C++

 

That is potentially what a manufacturer would conclude, and we would be required to use 'new' for everything if we want to use their headers. Most likely they would not do that, but its not hard to imagine they end up using other ideas that are also not so great.

 

 

 

>That should make it fairly easy to do pretty much anything one wants,

 

You can use the existing headers, but you end up in an awkward spot because they may not give you any register bit position information in the register struct they provide (register wide names only). So now you have the rest of the needed information scattered in various enums and simple defines, as bitmasks and bit positions (with names created in a way as to avoid all the other define names- so names are larger than really needed). In the end it becomes easier to just create your own register layout and enums.

 

There are times it is not so bad to use existing headers, other times it just doesn't work very well. With only a little work, you can use something that ends up being easier to read and understand-

(pinctrl is a reference to the correct PINnCTRL register)-

 

SA  inMode   (PINS::ISCMODE e) { pinctrl.ISC = e; }
SA  pullupOn ()                { pinctrl.PULLUP = 1; }

SA  pullupOff()                { pinctrl.PULLUP = 0; }

 

sw.inMode( BOTHEDGES );

 

using existing headers-

SA  inMode   (PORT_ISC_t e)    { pinctrl = (pinctrl bitand ~PORT_ISC_gm) bitor e; }
SA  pullupOn ()                { pinctrl or_eq PORT_PULLUPEN_bm; }

SA  pullupOff()                { pinctrl and_eq ~PORT_PULLUPEN_bm; }

 

sw.inMode ( PORT_ISC_BOTHEDGES_gc );

 

They both will end up doing the same thing, but the former is easier to read as you do not need to spell out to the compiler how to clear/set bits inside a register. 

 

An example for an nRF52 Gpio-

https://github.com/cv007/nRF52_B...

I'm not sure what it would look like if using the supplied headers, but you will end up digging around various headers just to find what you are looking for and still end up with names that are 30 chars wide with a handful of underscores. Easier to just spend the 60 minutes and create your own register struct and required enums and be done with it, never again needing to use/look at the manufacturers header(s) for that peripheral. 

 

Last Edited: Wed. Dec 2, 2020 - 08:15 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

curtvm wrote:
>That should make it fairly easy to do pretty much anything one wants,

 

You can use the existing headers, but you end up in an awkward spot because they may not give you any register bit position information in the register struct they provide (register wide names only). So now you have the rest of the needed information scattered in various enums and simple defines, as bitmasks and bit positions (with names created in a way as to avoid all the other define names- so names are larger than really needed). In the end it becomes easier to just create your own register layout and enums.

IIRC none of avr-gcc's #defines are more than eight characters.

Again IIRC they are derived from manufacturer data files.

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
IIRC none of avr-gcc's #defines are more than eight characters.
Oh?

<SNIP>
#define ADC_PRESCALER_gm  0x07  /* Clock Prescaler Selection group mask. */
#define ADC_PRESCALER_gp  0  /* Clock Prescaler Selection group position. */
#define ADC_PRESCALER0_bm  (1<<0)  /* Clock Prescaler Selection bit 0 mask. */
#define ADC_PRESCALER0_bp  0  /* Clock Prescaler Selection bit 0 position. */
#define ADC_PRESCALER1_bm  (1<<1)  /* Clock Prescaler Selection bit 1 mask. */
#define ADC_PRESCALER1_bp  1  /* Clock Prescaler Selection bit 1 position. */
#define ADC_PRESCALER2_bm  (1<<2)  /* Clock Prescaler Selection bit 2 mask. */
#define ADC_PRESCALER2_bp  2  /* Clock Prescaler Selection bit 2 position. */
#define ADC_CH0IF_bm  0x01  /* Channel 0 Interrupt Flag bit mask. */
#define ADC_CH0IF_bp  0  /* Channel 0 Interrupt Flag bit position. */
#define ADC_CH1IF_bm  0x02  /* Channel 1 Interrupt Flag bit mask. */
#define ADC_CH1IF_bp  1  /* Channel 1 Interrupt Flag bit position. */
#define ADC_CH2IF_bm  0x04  /* Channel 2 Interrupt Flag bit mask. */
#define ADC_CH2IF_bp  2  /* Channel 2 Interrupt Flag bit position. */
#define ADC_CH3IF_bm  0x08  /* Channel 3 Interrupt Flag bit mask. */
#define ADC_CH3IF_bp  3  /* Channel 3 Interrupt Flag bit position. */
#define AES_XOR_bm  0x04  /* State XOR Load Enable bit mask. */
#define AES_XOR_bp  2  /* State XOR Load Enable bit position. */
#define AES_DECRYPT_bm  0x10  /* Decryption / Direction bit mask. */
#define AES_DECRYPT_bp  4  /* Decryption / Direction bit position. */
#define AES_RESET_bm  0x20  /* AES Software Reset bit mask. */
#define AES_RESET_bp  5  /* AES Software Reset bit position. */
#define AES_AUTO_bm  0x40  /* Auto Start Trigger bit mask. */
#define AES_AUTO_bp  6  /* Auto Start Trigger bit position. */
#define AES_START_bm  0x80  /* Start/Run bit mask. */
#define AES_START_bp  7  /* Start/Run bit position. */
#define AES_SRIF_bm  0x01  /* State Ready Interrupt Flag bit mask. */
#define AES_SRIF_bp  0  /* State Ready Interrupt Flag bit position. */
#define AES_ERROR_bm  0x80  /* AES Error bit mask. */
#define AES_ERROR_bp  7  /* AES Error bit position. */
#define AES_INTLVL_gm  0x03  /* Interrupt level group mask. */
#define AES_INTLVL_gp  0  /* Interrupt level group position. */
#define AES_INTLVL0_bm  (1<<0)  /* Interrupt level bit 0 mask. */
#define AES_INTLVL0_bp  0  /* Interrupt level bit 0 position. */
#define AES_INTLVL1_bm  (1<<1)  /* Interrupt level bit 1 mask. */
#define AES_INTLVL1_bp  1  /* Interrupt level bit 1 position. */
#define AWEX_DTICCAEN_bm  0x01  /* Dead Time Insertion Compare Channel A Enable bit mask. */
#define AWEX_DTICCAEN_bp  0  /* Dead Time Insertion Compare Channel A Enable bit position. */
#define AWEX_DTICCBEN_bm  0x02  /* Dead Time Insertion Compare Channel B Enable bit mask. */
#define AWEX_DTICCBEN_bp  1  /* Dead Time Insertion Compare Channel B Enable bit position. */
#define AWEX_DTICCCEN_bm  0x04  /* Dead Time Insertion Compare Channel C Enable bit mask. */
#define AWEX_DTICCCEN_bp  2  /* Dead Time Insertion Compare Channel C Enable bit position. */
#define AWEX_DTICCDEN_bm  0x08  /* Dead Time Insertion Compare Channel D Enable bit mask. */
#define AWEX_DTICCDEN_bp  3  /* Dead Time Insertion Compare Channel D Enable bit position. */
#define AWEX_CWCM_bm  0x10  /* Common Waveform Channel Mode bit mask. */
#define AWEX_CWCM_bp  4  /* Common Waveform Channel Mode bit position. */
#define AWEX_PGM_bm  0x20  /* Pattern Generation Mode bit mask. */
#define AWEX_PGM_bp  5  /* Pattern Generation Mode bit position. */
#define AWEX_FDACT_gm  0x03  /* Fault Detect Action group mask. */
#define AWEX_FDACT_gp  0  /* Fault Detect Action group position. */
#define AWEX_FDACT0_bm  (1<<0)  /* Fault Detect Action bit 0 mask. */
#define AWEX_FDACT0_bp  0  /* Fault Detect Action bit 0 position. */
#define AWEX_FDACT1_bm  (1<<1)  /* Fault Detect Action bit 1 mask. */
#define AWEX_FDACT1_bp  1  /* Fault Detect Action bit 1 position. */
#define AWEX_FDMODE_bm  0x04  /* Fault Detect Mode bit mask. */
#define AWEX_FDMODE_bp  2  /* Fault Detect Mode bit position. */
#define AWEX_FDDBD_bm  0x10  /* Fault Detect on Disable Break Disable bit mask. */
#define AWEX_FDDBD_bp  4  /* Fault Detect on Disable Break Disable bit position. */
#define AWEX_DTLSBUFV_bm  0x01  /* Dead Time Low Side Buffer Valid bit mask. */
#define AWEX_DTLSBUFV_bp  0  /* Dead Time Low Side Buffer Valid bit position. */
#define AWEX_DTHSBUFV_bm  0x02  /* Dead Time High Side Buffer Valid bit mask. */
#define AWEX_DTHSBUFV_bp  1  /* Dead Time High Side Buffer Valid bit position. */
#define AWEX_FDF_bm  0x04  /* Fault Detect Flag bit mask. */
#define AWEX_FDF_bp  2  /* Fault Detect Flag bit position. */
#define CLK_SCLKSEL_gm  0x07  /* System Clock Selection group mask. */
#define CLK_SCLKSEL_gp  0  /* System Clock Selection group position. */
#define CLK_SCLKSEL0_bm  (1<<0)  /* System Clock Selection bit 0 mask. */
#define CLK_SCLKSEL0_bp  0  /* System Clock Selection bit 0 position. */
#define CLK_SCLKSEL1_bm  (1<<1)  /* System Clock Selection bit 1 mask. */
<SNIP>

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>IIRC none of avr-gcc's #defines are more than eight characters.

>Again IIRC they are derived from manufacturer data files.

 

avr-gcc is not providing headers for any avr0/1, which this already showed-

 

SA  inMode   (PORT_ISC_t e)    { pinctrl = (pinctrl bitand ~PORT_ISC_gm) bitor e; }
SA  pullupOn ()                { pinctrl or_eq
PORT_PULLUPEN_bm; }

SA  pullupOff()                { pinctrl and_eq ~PORT_PULLUPEN_bm; }

 

sw.inMode ( PORT_ISC_BOTHEDGES_gc );

 

That contains 3 items from the manufacturer supplied header, which are more than 8 characters each. Since these enums and defines are 'global', they have to use a naming scheme that prevent collisions with everything else. They all end up like that because that is the nature of the programming language used, and we end up with a truckload of things like NRF_GPIO_PIN_SENSE_HIGH where the caps lock and underscore keys get worn out.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

skeeve wrote:

IIRC none of avr-gcc's #defines are more than eight characters.

Oh?

I sit corrected.

Those are for xmegas, correct?

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There's a bit of a misty haze these days about exactly where "Xmega" ends. I could for example look at "Tiny"3216 for example and equally find:

C:\Program Files (x86)\Atmel\Studio\7.0\packs\atmel\ATtiny_DFP\1.4.310\include\avr>grep #define iotn3216.h | grep SFR_MEM8
#define CCP  _SFR_MEM8(0x0034)  /* Configuration Change Protection */
#define SPH  _SFR_MEM8(0x003E)  /* Stack Pointer High */
#define SPL  _SFR_MEM8(0x003D)  /* Stack Pointer Low */
#define SREG  _SFR_MEM8(0x003F)  /* Status Register */
#define GPIOR0  _SFR_MEM8(0x001C)  /* General Purpose IO Register 0 */
#define GPIOR1  _SFR_MEM8(0x001D)  /* General Purpose IO Register 1 */
#define GPIOR2  _SFR_MEM8(0x001E)  /* General Purpose IO Register 2 */
#define GPIOR3  _SFR_MEM8(0x001F)  /* General Purpose IO Register 3 */
#define GPIO0  _SFR_MEM8(0x001C)  /* General Purpose IO Register 0 */
#define GPIO1  _SFR_MEM8(0x001D)  /* General Purpose IO Register 1 */
#define GPIO2  _SFR_MEM8(0x001E)  /* General Purpose IO Register 2 */
#define GPIO3  _SFR_MEM8(0x001F)  /* General Purpose IO Register 3 */
#define VPORTA_DIR  _SFR_MEM8(0x0000)
#define VPORTA_OUT  _SFR_MEM8(0x0001)
#define VPORTA_IN  _SFR_MEM8(0x0002)
#define VPORTA_INTFLAGS  _SFR_MEM8(0x0003)
#define VPORTB_DIR  _SFR_MEM8(0x0004)
#define VPORTB_OUT  _SFR_MEM8(0x0005)
#define VPORTB_IN  _SFR_MEM8(0x0006)
#define VPORTB_INTFLAGS  _SFR_MEM8(0x0007)
#define VPORTC_DIR  _SFR_MEM8(0x0008)
#define VPORTC_OUT  _SFR_MEM8(0x0009)
#define VPORTC_IN  _SFR_MEM8(0x000A)
#define VPORTC_INTFLAGS  _SFR_MEM8(0x000B)
#define GPIO_GPIOR0  _SFR_MEM8(0x001C)
#define GPIO_GPIOR1  _SFR_MEM8(0x001D)
#define GPIO_GPIOR2  _SFR_MEM8(0x001E)
#define GPIO_GPIOR3  _SFR_MEM8(0x001F)
#define GPIO_GPIO0  _SFR_MEM8(0x001C)
#define GPIO_GPIO1  _SFR_MEM8(0x001D)
#define GPIO_GPIO2  _SFR_MEM8(0x001E)
#define GPIO_GPIO3  _SFR_MEM8(0x001F)
#define CPU_CCP  _SFR_MEM8(0x0034)
#define CPU_SPL  _SFR_MEM8(0x003D)
#define CPU_SPH  _SFR_MEM8(0x003E)
#define CPU_SREG  _SFR_MEM8(0x003F)
#define RSTCTRL_RSTFR  _SFR_MEM8(0x0040)
#define RSTCTRL_SWRR  _SFR_MEM8(0x0041)
#define SLPCTRL_CTRLA  _SFR_MEM8(0x0050)
#define CLKCTRL_MCLKCTRLA  _SFR_MEM8(0x0060)
#define CLKCTRL_MCLKCTRLB  _SFR_MEM8(0x0061)
#define CLKCTRL_MCLKLOCK  _SFR_MEM8(0x0062)
#define CLKCTRL_MCLKSTATUS  _SFR_MEM8(0x0063)
#define CLKCTRL_OSC20MCTRLA  _SFR_MEM8(0x0070)
#define CLKCTRL_OSC20MCALIBA  _SFR_MEM8(0x0071)
#define CLKCTRL_OSC20MCALIBB  _SFR_MEM8(0x0072)
#define CLKCTRL_OSC32KCTRLA  _SFR_MEM8(0x0078)
#define CLKCTRL_XOSC32KCTRLA  _SFR_MEM8(0x007C)
#define BOD_CTRLA  _SFR_MEM8(0x0080)
#define BOD_CTRLB  _SFR_MEM8(0x0081)
#define BOD_VLMCTRLA  _SFR_MEM8(0x0088)
#define BOD_INTCTRL  _SFR_MEM8(0x0089)
#define BOD_INTFLAGS  _SFR_MEM8(0x008A)
#define BOD_STATUS  _SFR_MEM8(0x008B)
#define VREF_CTRLA  _SFR_MEM8(0x00A0)
#define VREF_CTRLB  _SFR_MEM8(0x00A1)
#define VREF_CTRLC  _SFR_MEM8(0x00A2)
#define VREF_CTRLD  _SFR_MEM8(0x00A3)
#define WDT_CTRLA  _SFR_MEM8(0x0100)
#define WDT_STATUS  _SFR_MEM8(0x0101)
#define CPUINT_CTRLA  _SFR_MEM8(0x0110)
#define CPUINT_STATUS  _SFR_MEM8(0x0111)
#define CPUINT_LVL0PRI  _SFR_MEM8(0x0112)
#define CPUINT_LVL1VEC  _SFR_MEM8(0x0113)
#define CRCSCAN_CTRLA  _SFR_MEM8(0x0120)
#define CRCSCAN_CTRLB  _SFR_MEM8(0x0121)
#define CRCSCAN_STATUS  _SFR_MEM8(0x0122)
#define RTC_CTRLA  _SFR_MEM8(0x0140)
#define RTC_STATUS  _SFR_MEM8(0x0141)
#define RTC_INTCTRL  _SFR_MEM8(0x0142)
#define RTC_INTFLAGS  _SFR_MEM8(0x0143)
#define RTC_TEMP  _SFR_MEM8(0x0144)
#define RTC_DBGCTRL  _SFR_MEM8(0x0145)
#define RTC_CLKSEL  _SFR_MEM8(0x0147)
#define RTC_CNTL  _SFR_MEM8(0x0148)
#define RTC_CNTH  _SFR_MEM8(0x0149)
#define RTC_PERL  _SFR_MEM8(0x014A)
#define RTC_PERH  _SFR_MEM8(0x014B)
#define RTC_CMPL  _SFR_MEM8(0x014C)
#define RTC_CMPH  _SFR_MEM8(0x014D)
#define RTC_PITCTRLA  _SFR_MEM8(0x0150)
#define RTC_PITSTATUS  _SFR_MEM8(0x0151)
#define RTC_PITINTCTRL  _SFR_MEM8(0x0152)
#define RTC_PITINTFLAGS  _SFR_MEM8(0x0153)
#define RTC_PITDBGCTRL  _SFR_MEM8(0x0155)
#define EVSYS_ASYNCSTROBE  _SFR_MEM8(0x0180)
#define EVSYS_SYNCSTROBE  _SFR_MEM8(0x0181)
<SNIP>

Sure it starts out with good intentions but the 8 character limit is soon broken because, (don't tell anyone!) but the things Microchip call "Tiny" and "Mega" these days are all really Xmega anyway ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steve17 wrote:

El Tangas wrote:

Well, if you look at the Arduino AVR source code, new is basically just a wrapper for malloc.

People who put malloc on a microcontroller are highly likely to be clueless.  You might want to avoid Arduino source code.

 

Well, for those of us who are balding, reading Arduino source code isn't quite so painful.  :-)

 

I have no special talents.  I am only passionately curious. - Albert Einstein

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:

I infer from the data members of Port_device,

that Xmega pin port registers are grouped by port.

 

IIRC that is not the case with all megas.

Yes,all Xmega ports are clones.  In c++ lingo there is just one port class, but there are multiple instances of the class.  The same is true of all the devices that use port pins, like usarts and counter timers.

 

Yes I know the mega devices with multiple instances are not alike.  That's the major reason I switched from megas to xmegas.  Atmel's hardware guys learned their lesson.  I'd think making them all the same would simplify designing the chip layout.  Atmel's software department hasn't figured that out yet.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
BTW is Register unsigned char?
Yes.  I've included myTypes.h in my source files for 35 years.  Needed back then for writing portable code.   

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

There's a bit of a misty haze these days about exactly where "Xmega" ends. I could for example look at "Tiny"3216 for example and equally find:

C:\Program Files (x86)\Atmel\Studio\7.0\packs\atmel\ATtiny_DFP\1.4.310\include\avr>grep #define iotn3216.h | grep SFR_MEM8
#define CCP  _SFR_MEM8(0x0034)  /* Configuration Change Protection */
#define SPH  _SFR_MEM8(0x003E)  /* Stack Pointer High */
      .
      . 
      .    
#define EVSYS_ASYNCSTROBE  _SFR_MEM8(0x0180)
#define EVSYS_SYNCSTROBE  _SFR_MEM8(0x0181)
<SNIP>

Sure it starts out with good intentions but the 8 character limit is soon broken because, (don't tell anyone!) but the things Microchip call "Tiny" and "Mega" these days are all really Xmega anyway ;-)

If I was being paid by the size of the source files, I'd include a lot of those header files. smiley