Author Topic: SAMD11 I2C driver alone consumes 12KB out of 16KB and facing ROM overflow error  (Read 5883 times)

0 Members and 1 Guest are viewing this topic.

Offline muthukural001Topic starter

  • Regular Contributor
  • *
  • Posts: 211
  • Country: in
Hello,

I am working on SAMD11D14A microcontroller and facing error that "Region rom overflowed by bytes" when I use I2C driver and its related function. Without "configure_i2c_master()" in main function, program memory usage is about 1.5KB which is equivalent to 12% of total 16KB. If I include the function, memory usage comes to 12KB which is equivalent to 70% of total 16KB. When I try to add other I2C related functions, I am getting that "Region rom overflowed by bytes"error. Please suggest that how to solve this. Attached code and error image.

main function:
Code: [Select]
int main (void)
{
 
system_init();

        configure_i2c_master();  //including this consumes 12 KB out of 16KB.Without this function, memory usage is about 1.5 KB

while(1);


}

I2C Init function:

Code: [Select]
void configure_i2c_master(void)
{
struct i2c_master_config config_i2c_master;
i2c_master_get_config_defaults(&config_i2c_master);
config_i2c_master.buffer_timeout = 10000;
config_i2c_master.pinmux_pad0=I2C_SDA_SERCOM_PINMUX_PAD0;
config_i2c_master.pinmux_pad1=I2C_SCL_SERCOM_PINMUX_PAD1;
i2c_master_init(&i2c_master_instance, CONF_I2C_MASTER_MODULE,&config_i2c_master);
i2c_master_enable(&i2c_master_instance);
}



Thanks,
Muthu

 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11236
  • Country: us
    • Personal site
This is a very small device, just write bare metal drivers. You will get nowhere with frameworks.

That is not that function fault. It just includes a lot of dependencies. If your device was bigger, then any additional drivers would benefit from those dependencies. But you are working with a very small device, so approach it as such.
Alex
 

Offline technix

  • Super Contributor
  • ***
  • Posts: 3507
  • Country: cn
  • From Shanghai With Love
    • My Untitled Blog
Ahhh the same problem I faced with USB on STM32F042. Bloated frameworks, the struggle is real buddy.

Way too often it is easier and smaller to skip the bulk of vendor libraries and frameworks. (For me with ARM I only keep CMSIS and the device-specific headers for peripheral registers.) I2C should be simple enough to write yourself manipulating the registers directly (even bit bang if you are feeling brave.)

Speaking of, I wonder what convoluted programming (anti-)paradigm it is to use those init structures and separate calls? That is not emulating register manipulation, neither matching the "common sense" of application programming (mostly object-oriented programming for me - I have a strong habit of OO, even OO using plain C.)
 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11236
  • Country: us
    • Personal site
Speaking of, I wonder what convoluted programming (anti-)paradigm it is to use those init structures and separate calls?
In theory, this allows for easy expansion of the capabilities. You can add new members to the structure without affecting functionality of the existing code. Init call will set those new members to their default state, so the whole thing will remain compatible with the old code.

In practice, it just creates bloat.
Alex
 

Offline technix

  • Super Contributor
  • ***
  • Posts: 3507
  • Country: cn
  • From Shanghai With Love
    • My Untitled Blog
Speaking of, I wonder what convoluted programming (anti-)paradigm it is to use those init structures and separate calls?
In theory, this allows for easy expansion of the capabilities. You can add new members to the structure without affecting functionality of the existing code. Init call will set those new members to their default state, so the whole thing will remain compatible with the old code.

In practice, it just creates bloat.
I would prefer using getters/setters over that kind of convoluted structure. Getters/setters look like more code, but they are subjected to finer grained garbage collection thus do not generate any bloat unless the code paths are actually used. Those can also be forced inline to eliminate function call overhead. The assertions, while useful, can be modified to make use of compiler builtins (GCC and LLVM sure have those, I wonder if Keil version 5 have them) to maximize optimization. The current structure initializers retain all code regardless whether you actually changed the field or not. There is no way the compiler can optimize any effective dead code away as from the look of the initializers all code are used. Also, getters/setters are generally better OO than those structure initializers, and they are also future proof as old code never calls a new getter/setter.

Something like this: (I am using Objective-C syntax - hopefully it is easy to understand)
Code: [Select]
@interface STMSerial : NSObject

@property (assign) uint16_t baudrate;
@property (assign) uint8_t frameLength;
@property (assign) uint8_t stopBits;
@property (assign) uint8_t parity;
// ...

- (instancetype)init; // Sets everything to some kind of default.
- (void)start;
- (void)stop;
- (void)send:(uint8_t)character;
- (uint8_t)receive;
// ...

@end

Or on an lower level, just keep the peripheral registers compatible across series. This way there would be no need for such abstraction. Especially for ARM microcontrollers where there is 4GB of memory address space to blow away.
« Last Edit: November 20, 2017, 07:51:53 pm by technix »
 

Offline mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 13736
  • Country: gb
    • Mike's Electric Stuff
Why would you bother with a manufacturer library for something as simple as I2C?
More so one that is clearly so badly written it takes 12K

Just read the datasheet and look for standalone example code.
Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Quote
I would prefer using getters/setters over that kind of convoluted structure.
The current "Atmel Start" code seems to put getter/setters underneath their bottom layers.  2+Mbyte worth, by the time they've done one for every hardware register of every peripheral for the particular chip you're using.
Code: [Select]
static inline void hri_sercomspi_clear_INTEN_DRE_bit(const void *const hw) { ((Sercom *)hw)->SPI.INTENCLR.reg = SERCOM_SPI_INTENSET_DRE; } static inline void hri_sercomspi_set_INTEN_TXC_bit(const void *const hw) { ((Sercom *)hw)->SPI.INTENSET.reg = SERCOM_SPI_INTENSET_TXC; } static inline bool hri_sercomspi_get_INTEN_TXC_bit(const void *const hw) { return (((Sercom *)hw)->SPI.INTENSET.reg & SERCOM_SPI_INTENSET_TXC) >> SERCOM_SPI_INTENSET_TXC_Pos; }
much like you'd get if you told a new hire "I heart the latest thing is getter/setter functions for everything.  Do that!  Better yet, figure out how to do that automatically from the CMSIS .SCD files!"
It does not seem very worthwhile to me!   At least they're inlined.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Hmm.  11k sounded like too much even for ASF, so I investigated a bit further.   Here's the biggest functions in the SAMD11 Xplained Prot I2C Master (polled) Example project, as reported by the "nm" utility:
Code: [Select]
00000b24 00000040 T system_gclk_gen_enable
00000728 00000044 T sercom_set_gclk_generator
000029d0 0000004c T __libc_init_array
00000c1c 00000058 T system_gclk_chan_disable
000028b8 0000006c T __aeabi_d2iz
00002924 00000070 T __aeabi_ui2d
00000ef8 00000074 T main
00000cc4 00000084 t _system_pinmux_config
00000b64 00000088 T system_gclk_gen_get_hz
00000000 0000008c T exception_table
00000868 0000008c T system_clock_source_get_hz
00000e68 00000090 T configure_i2c_master
000009c0 00000090 T system_clock_init
00000930 00000090 T system_clock_source_enable
00000a7c 000000a8 T system_gclk_gen_set_config
0000076c 000000b4 T _sercom_get_default_pad
00000dac 000000bc T Reset_Handler
00000638 000000c8 t _i2c_master_write_packet
00000f80 0000010a T __udivsi3
000004cc 0000016c t _i2c_master_read_packet
000001b0 000002a0 T i2c_master_init
00001d1c 000004f4 T __aeabi_dmul
000016f0 0000062c T __aeabi_ddiv
00001098 00000658 T __aeabi_dadd
00002210 000006a8 T __aeabi_dsub
The I2C and initialization code is not really what I'd call "compact", but what's really hurting things is the 6k+ of double precision floating point math...

Because apparently  _i2c_master_set_config() does this poorly documented:
Code: [Select]
tmp_baud = (int32_t)(div_ceil(
fgclk - fscl * (10 + fgclk * trise * 0.000000001), 2 * fscl));

/* For High speed mode, set the SCL ratio of high:low to 1:2. */
if (config->transfer_speed == I2C_MASTER_SPEED_HIGH_SPEED) {
tmp_baudlow_hs = (int32_t)((fgclk * 2.0) / (3.0 * fscl_hs) - 1);
if (tmp_baudlow_hs) {
tmp_baud_hs = (int32_t)(fgclk / fscl_hs) - 2 - tmp_baudlow_hs;
} else {
tmp_baud_hs = (int32_t)(div_ceil(fgclk, 2 * fscl_hs)) - 1;
}
}
I guess the code doesn't want to allow you to set a bitrate that is inconsistent with the rise times you've configured.  A wonderful reason to include 6k of floating point code!

A FINE example of why I don't trust ASF at all...  (I think it's the normal Async USART code that pulls in the (not-ARM-optimized) 64bit integer math functions that resulted in one of my first "WTF?" moments...)
« Last Edit: November 21, 2017, 04:23:31 am by westfw »
 
The following users thanked this post: Someone

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11236
  • Country: us
    • Personal site
Because apparently  _i2c_master_set_config() does this poorly documented:
Oh, wow.

And the funny thing is that all those formulas for Trise don't appear to work for all cases. The most reliable way of setting the frequency is still just a manual selection of a baudrate register and an actual measurement of the result.
Alex
 

Offline muthukural001Topic starter

  • Regular Contributor
  • *
  • Posts: 211
  • Country: in
Hello,

Thanks. Yes, as you said, function _i2c_master_set_config() has floating point multiplications and divisions, and the device doesn’t support it, so the project links libgcc, this cause the large code size.So, I went to i2c_master.c ,remove those formulas and calculated baud rate manually and apply it as shown in the attached image.After I have done this, code size came down to 35%(5.8KB) from 70% (12KB). Also, I don't want to go with ASF to make  the simple code.


I2C CLOCK 100KHZ:
Code: [Select]
BAUD + BAUDLOW = fGCLK / FSCL – (fGCLK TRISE) – 10
= 8M/100k – (8M x 215ns) – 10
= 68

Thanks,
Muthu
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Sigh.  It's not that I've conducted an in-depth study of ASF (or any other vendor's libraries) and reached a careful conclusion that they are inadequate.   But every time I "sample" some random area of the code, I quickly run into things that are awful. :-(

This particular sort of thing; calculating a BRG divisor/etc, is the sort of thing that most 8-bit microcontroller programmers would arrange to have happen at compile time, where - even if you used floating point math - things would be optimized away to a simple load/store of a constant.  It's not like I2C has a lot of common bitrates, or needs to change bitrate at runtime, or needs to run at the same bitrate as the CPU changes clockrates (for some reason?)  All things that the ASF code allows for, and makes you pay for by its very styling - whether you want it or not.

I'm glad my analysis helped your project (and thanks for the update!)

(On the plus side, the way that Atmel Studio copies individual ASF files into your project makes it easy to edit them to better suit a particular need.  The source-code-control part of me is somewhat happy, even if the "storage efficiency" part of me cringes.)
 

Offline mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 13736
  • Country: gb
    • Mike's Electric Stuff

The I2C and initialization code is not really what I'd call "compact", but what's really hurting things is the 6k+ of double precision floating point math...

This reinforces my belief that this sort of code is left to interns who don't have a clue about embedded programming.
Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Gah! That kind of beginner coding makes the Arduino libraries look good.
 

Offline Rasz

  • Super Contributor
  • ***
  • Posts: 2616
  • Country: 00
    • My random blog.
The I2C and initialization code is not really what I'd call "compact", but what's really hurting things is the 6k+ of double precision floating point math...

higher tier microcontrollers dont sell themselves
Vivado bloat is another great example, cant find it right now, but I saw somewhere comparison suggesting Vivado added ~50-100% garbage padding to force you $$ upstream.
Who logs in to gdm? Not I, said the duck.
My fireplace is on fire, but in all the wrong places.
 

Offline Someone

  • Super Contributor
  • ***
  • Posts: 4525
  • Country: au
    • send complaints here
This particular sort of thing; calculating a BRG divisor/etc, is the sort of thing that most 8-bit microcontroller programmers would arrange to have happen at compile time, where - even if you used floating point math - things would be optimized away to a simple load/store of a constant.  It's not like I2C has a lot of common bitrates, or needs to change bitrate at runtime, or needs to run at the same bitrate as the CPU changes clockrates (for some reason?)  All things that the ASF code allows for, and makes you pay for by its very styling - whether you want it or not.
Being able to change speeds of the SPI interface on the fly is very useful, but they way they implemented it is hilarious. You could have two functions one that takes constants and one that takes variables but shouldn't the compiler be able to sweep all that efficiency away?

Vivado bloat is another great example, cant find it right now, but I saw somewhere comparison suggesting Vivado added ~50-100% garbage padding to force you $$ upstream.
When you compare the same code being implemented on Vivado compared to other tools its not stand out better or worse. Be careful of the comparisons people have made synthesising their own IP functions that are "the same" as the integrated ones from Xilinx, its almost certain the code is not compatible or functionally equivalent but just enough to support their use and provide impressive numbers.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Quote
shouldn't the compiler be able to sweep all that efficiency away?
Yes, except that they've eschewed inlines, and their apparently-preferred structure is:

Code: [Select]
struct xxx_config_t *config;
config = xxx_get_default_config();
config->yyparam = <thingIWantDifferentFromDefault>;
xxx_do_config(xxxinstance, config);

which makes it impossible (or at least very difficult) to do optimization based on some values being constant (or default.)   Grr.

The SAMD11 IO example in Atmel Start (which is sort-of ASF 4.x) is just as bad (actually, it ends up including both single precision and double-precision float functions!), but there does claim to be a "lite" I2C implementation as well.  A null LITE I2C project seems to be pretty small; I'm not sure how close it is to doing anything useful.
 

Offline technix

  • Super Contributor
  • ***
  • Posts: 3507
  • Country: cn
  • From Shanghai With Love
    • My Untitled Blog
Quote
shouldn't the compiler be able to sweep all that efficiency away?
Yes, except that they've eschewed inlines, and their apparently-preferred structure is:

Code: [Select]
struct xxx_config_t *config;
config = xxx_get_default_config();
config->yyparam = <thingIWantDifferentFromDefault>;
xxx_do_config(xxxinstance, config);

which makes it impossible (or at least very difficult) to do optimization based on some values being constant (or default.)   Grr.

The SAMD11 IO example in Atmel Start (which is sort-of ASF 4.x) is just as bad (actually, it ends up including both single precision and double-precision float functions!), but there does claim to be a "lite" I2C implementation as well.  A null LITE I2C project seems to be pretty small; I'm not sure how close it is to doing anything useful.
If they used macros for structure initializers, wrapped code in if’s, and force inlined the init functions, with aggressive compiler dead code elimination those may be removed. (I need to test this with ARM GCC 6, AVR GCC 7 and Apple clang/LLVM 6 though.)
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Quote
If they used macros for structure initializers, wrapped code in if’s, and force inlined the init functions
perhaps.   Sometime in the distant past, here, while we were complaining about the ST libraries, I think I demonstrated how to get some significant code savings just by #include-ing the .c code instead of the .h (thereby permitting the compiler to inline and eliminate dead code.)  Um...   https://www.eevblog.com/forum/microcontrollers/stm32-ghetto-style/msg522553/#msg522553

But they don't.  They don't even suggest static initializer structures.

(OTOH, code like that can get really ugly, really fast.  In some ways, perhaps it's better that the vendor code be stupid and obvious, rather than clever and opaque.)
 

Offline technix

  • Super Contributor
  • ***
  • Posts: 3507
  • Country: cn
  • From Shanghai With Love
    • My Untitled Blog
Quote
If they used macros for structure initializers, wrapped code in if’s, and force inlined the init functions
perhaps.   Sometime in the distant past, here, while we were complaining about the ST libraries, I think I demonstrated how to get some significant code savings just by #include-ing the .c code instead of the .h (thereby permitting the compiler to inline and eliminate dead code.)  Um...   https://www.eevblog.com/forum/microcontrollers/stm32-ghetto-style/msg522553/#msg522553

But they don't.  They don't even suggest static initializer structures.

(OTOH, code like that can get really ugly, really fast.  In some ways, perhaps it's better that the vendor code be stupid and obvious, rather than clever and opaque.)
That is why for my personal projects I never use any vendor libraries, only CMSIS and the device header file. I may take pieces of code from the vendor library, but those are almost always hand DCE'd.

The device header file is the most future proof format IMO. There is no point wrapping around the hardware unless you are conforming to some kind of standard (like POSIX.)
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf