SAMD11 I2C driver alone consumes 12KB out of 16KB and facing  ROM overflow error - Page 1
Forum section: electronics / general

author: muthukural001
author_rank: Regular Contributor
timestamp: November 20, 2017, 11:56:02 am
content: 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: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 KBwhile(1);}I2C Init function:Code: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

author: ataradov
author_rank: Super Contributor
timestamp: November 20, 2017, 05:35:22 pm
content: 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.

author: technix
author_rank: Super Contributor
timestamp: November 20, 2017, 07:16:33 pm
content: 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.)

author: ataradov
author_rank: Super Contributor
timestamp: November 20, 2017, 07:20:35 pm
content: Quote from: technix on November 20, 2017, 07:16:33 pmIn 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.

author: technix
author_rank: Super Contributor
timestamp: November 20, 2017, 07:44:27 pm
content: Quote from: ataradov on November 20, 2017, 07:20:35 pmI 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:@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;// ...@endOr 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.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: November 21, 2017, 12:18:37 am
content: 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 12KJust read the datasheet and look for standalone example code.

author: westfw
author_rank: Super Contributor
timestamp: November 21, 2017, 01:58:16 am
content: QuoteThe 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: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.

author: westfw
author_rank: Super Contributor
timestamp: November 21, 2017, 04:12:44 am
content: 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:00000b24 00000040 T system_gclk_gen_enable00000728 00000044 T sercom_set_gclk_generator000029d0 0000004c T __libc_init_array00000c1c 00000058 T system_gclk_chan_disable000028b8 0000006c T __aeabi_d2iz00002924 00000070 T __aeabi_ui2d00000ef8 00000074 T main00000cc4 00000084 t _system_pinmux_config00000b64 00000088 T system_gclk_gen_get_hz00000000 0000008c T exception_table00000868 0000008c T system_clock_source_get_hz00000e68 00000090 T configure_i2c_master000009c0 00000090 T system_clock_init00000930 00000090 T system_clock_source_enable00000a7c 000000a8 T system_gclk_gen_set_config0000076c 000000b4 T _sercom_get_default_pad00000dac 000000bc T Reset_Handler00000638 000000c8 t _i2c_master_write_packet00000f80 0000010a T __udivsi3000004cc 0000016c t _i2c_master_read_packet000001b0 000002a0 T i2c_master_init00001d1c 000004f4 T __aeabi_dmul000016f0 0000062c T __aeabi_ddiv00001098 00000658 T __aeabi_dadd00002210 000006a8 T __aeabi_dsubThe 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: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...)

author: ataradov
author_rank: Super Contributor
timestamp: November 21, 2017, 04:18:07 am
content: Quote from: westfw on November 21, 2017, 04:12:44 amOh, 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.

author: muthukural001
author_rank: Regular Contributor
timestamp: November 21, 2017, 06:41:02 am
content: 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:BAUD + BAUDLOW = fGCLK / FSCL – (fGCLK TRISE) – 10= 8M/100k – (8M x 215ns) – 10= 68Thanks,Muthu

author: westfw
author_rank: Super Contributor
timestamp: November 21, 2017, 07:43:41 am
content: 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.)

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: November 21, 2017, 10:11:35 am
content: Quote from: westfw on November 21, 2017, 04:12:44 amThis reinforces my belief that this sort of code is left to interns who don't have a clue about embedded programming.

author: brucehoult
author_rank: Super Contributor
timestamp: November 21, 2017, 12:24:43 pm
content: Gah! That kind of beginner coding makes the Arduino libraries look good.

author: Rasz
author_rank: Super Contributor
timestamp: November 21, 2017, 09:47:33 pm
content: Quote from: westfw on November 21, 2017, 04:12:44 amhigher tier microcontrollers dont sell themselvesVivado 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.

author: Someone
author_rank: Super Contributor
timestamp: November 21, 2017, 10:36:06 pm
content: Quote from: westfw on November 21, 2017, 07:43:41 amBeing 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?Quote from: Rasz on November 21, 2017, 09:47:33 pmWhen 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.

author: westfw
author_rank: Super Contributor
timestamp: November 22, 2017, 04:05:45 am
content: QuoteYes, except that they've eschewed inlines, and their apparently-preferred structure is:Code: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.

author: technix
author_rank: Super Contributor
timestamp: November 22, 2017, 07:08:44 am
content: Quote from: westfw on November 22, 2017, 04:05:45 amIf 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.)

author: westfw
author_rank: Super Contributor
timestamp: November 22, 2017, 09:43:13 am
content: Quoteperhaps. 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/#msg522553But 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.)

author: technix
author_rank: Super Contributor
timestamp: November 22, 2017, 11:31:37 am
content: Quote from: westfw on November 22, 2017, 09:43:13 amThat 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.)