Benefits of using both I2C of STM32 , 8bit vs 32bit calculations - Page 1
Forum section: electronics / general

author: 3dgeo
author_rank: Frequent Contributor
timestamp: April 25, 2019, 12:34:16 pm
content: Hi,Does STM32 have 2 different I2C hardware inside or it's a same hardware that just use two different output pins? I'm asking cos in my circuit MCU constantly sends I2C data, but I want to add more I2C devices and don't want to disturb/delay already existing data flow. Another reason for using 2 I2C is that I'm considering using ICs with same I2C addresses and I don't want to add extra hardware for I2C multiplexing. Any code samples of using DMA with I2C?. Switching to SPI is not an option.I need to optimize my code so I'm trying to understand/use 32bit MCU advantages – there is no speed difference while using 1 byte vars vs 4 byte vars if result is still 4 byte var on 32bit CPU cos 32Bit CPU always uses 32Bit addresses, am I right?In my case STM32 has to calculate Pythagorean theorem "sqrt()" and "map()" function many times per second. I know that first advise will be to remove "sqrt()" and square up other values, but there are too many other values to square up. I should probably look in to "map()" and try to optimize it. Sorry for being so unspecific – I don't know what I'm looking for myself, please share if You have other suggestions.Thanks in advance.

author: Siwastaja
author_rank: Super Contributor
timestamp: April 25, 2019, 12:52:50 pm
content: Things named "I2C1", "I2C2", "I2C3" and so on are separate and fully autonomous. Very handy as you have found out. If the datasheet front page says "3 I2Cs", then it means three separate peripherals.Furthermore, each separate I2C peripheral is often mapped to a few (often just max two) choices of IO pins (alternate function mappings). In this case, you need to choose which one you use, and while you could change it "on the fly", you can't simultaneously access different devices, and even switchovers can be tricky and need careful consideration, because the peripheral is the same.Yes, typically no speed benefit by using a smaller variable, but sometimes, in a tight data processing loop, the compiler is able to optimize using SIMD instructions, which would mean things like adding four 8-bit numbers aligned next to each other in a 32-bit register together in a single instruction. The greatest speed benefit now happens as you can LOAD four bytes in one memory bus operation; the actual SIMD calculation is then a small bonus. Often you need to design your data structures and algorithms properly to allow this to happen; sometimes even write your own in assembler. Availability of SIMD instructions depends on your CPU; M0 doesn't have a lot, while M7 has an astonishing set (like a DSP).sqrt "many times per second" doesn't sound too bad. If you said tens of times permillisecond, I'd be more concerned. Of course depending on the CPU (an M0 at 8MHz? Or an M7 at 400MHz?). For example, Cortex M7 has hardware sqrt instruction.

author: NorthGuy
author_rank: Super Contributor
timestamp: April 25, 2019, 01:07:07 pm
content: If the CPU doesn't have an integer square root instruction, then square root must be emulated. In this case, 32-bit square rooting will take much longer than 8-bit square rooting.If your CPU has FPU, then it may have a floating point square root instruction, which is likely to be substantially faster than integer emulation.

author: 3dgeo
author_rank: Frequent Contributor
timestamp: April 25, 2019, 01:22:24 pm
content: Thank You for Your reply.Quote from: Siwastaja on April 25, 2019, 12:52:50 pmDatasheet says "Up to 2 x I2C interfaces (SMBus/PMBus)", so according to You it's 2 different hardware peripherals?Have any tips on using DMA? Can't find anything useful on google.I know DMA is supported with this MCU: "They can be served by DMA and they support SM Bus 2.0/PM Bus.", "they"– I²C bus interfaces.Quote from: Siwastaja on April 25, 2019, 12:52:50 pmTo be honest I didn't calculated exact number, ideal is 50 000, tho I'll settle with the best number I can get (getting over 25 000 now).Quote from: NorthGuy on April 25, 2019, 01:07:07 pmI don't need 32bit square rooting, 16bit is fine, tho very good tip on hardware square rooting, will look in to that, thanks!

author: Siwastaja
author_rank: Super Contributor
timestamp: April 26, 2019, 05:52:49 pm
content: Quote from: 3dgeo on April 25, 2019, 01:22:24 pmYes, if the specific part has those 2 interfaces (note the words "up to" and the fact that the same datasheet is for many different devices).QuoteSome older STM32 MCUs have practically unusable DMA mode for I2C (even if they have the "tick in the box"); some newer ones have very well usable I2C DMA mode. Offhand, I can't say. Don't base your design on having DMA available before actually prototyping your software. Have a fallback possibility that you write it interrupt-driven. Usually not a problem; just don't use I2C in super high speed things, if you can limit yourself to the 400 kbit/s revision, you are usually fine even without DMA. Withany otherSTM32 MCU than the newest H7 (which has a complete DMA switch matrix), leave margin for the possibility that you simply don't have all the DMA mappings available, especially if you have many peripherals enabled and DMA'ing at the same time.

author: 3dgeo
author_rank: Frequent Contributor
timestamp: April 29, 2019, 02:15:49 am
content: Quote from: Siwastaja on April 26, 2019, 05:52:49 pmMCU I'm using is STM32F103C8T6, does it have usable DMA? I need to send 15 bytes array via I2C, code I'm using now is (I know I can do loop, but I think „brute force“ is a bit faster):Code:Wire.beginTransmission(0x60); // IC adressWire.write(0b10100010); // Write to the GRPPWM register AUTO_INCREMENT_BRIGHTNESSWire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.write(RGBbuffer);Wire.endTransmission();

author: alan.green@gmail.com
author_rank: Unknown
timestamp: April 29, 2019, 03:11:24 am
content: Quote from: 3dgeo on April 29, 2019, 02:15:49 amYou're using a blue pill, right?Have you tried STM32Cube? It will generate skeleton code to use peripherals and can be configured to do DMA. I haven't actually done DMA with it, but it's a starting point. It looks like STMicro recently rebranded their Eclipse-based IDE as STM32CubeIDE and the code generation tool is built right in.Just briefly: Download and install the IDE. Make a new STM32 project. Select your part (STM32F103C8) Add the I2C peripheral and configure it to use DMA for data transmission. Then save and generate code. In the generated code, look in a file named stm32f1xx_hal_i2c.h for a function named HAL_I2C_Master_Transmit_DMA().By the way, since DMA is a little tricky, and since performance is important to you, you might want to get comfortable with readingthe datasheetfor your part. It's a long document, but even a light reading of chapters relevant to you (i.e overview, CPU, DMA, I2C) will help you understand part-specific concepts and likely save you some debugging.

author: 3dgeo
author_rank: Frequent Contributor
timestamp: April 30, 2019, 05:35:07 pm
content: Thank You for detailed reply,I'm not familiar with STcube, I'm using ArduinoIDE. Yes, it's a same chip as on blue pill.I found a thread onstm32duino.comwhere people discuss using both I2C, but code posted there isn't working for me. I didn't expect that it will be so hard to use two hardware I2C.I'm getting error "no matching function for call to 'TwoWire::TwoWire(i2c_dev* const&, <anonymous enum>, <anonymous enum>)", they are probably using custom I2C lib, aren't they?

author: richardman
author_rank: Frequent Contributor
timestamp: April 30, 2019, 08:23:37 pm
content: re: I2C1, I2C2, ... "up to 2x I2C" etc.One confusing aspect of ST documentation is that thereference manualsdescribe the device families or series as a whole. For example, STM32F41x reference manual would describe functional features common to all STM32F41x devices (this is just an example, ST has their mystical way of grouping things into reference manuals). So it has to say "up to 2x I2C" or "up to 5 USART" etc. To know how many actual I2C a particular device, e.g. STM32F411RE, has, you have to look at thedata sheetfor that specific device.So do the right google search and you will find the correct doc.

author: 3dgeo
author_rank: Frequent Contributor
timestamp: May 11, 2019, 01:15:52 pm
content: Well, even this is an old thread – for most issues I found a very good solutions and I have to share them:Speed issue regarding "sqrt()":While googling I foundthisbit of code, I only need 8bit root, so I modified it to work only with 8bit:Code:byte sqrt_8bit(unsigned int n){byte c = 0b10000000;byte g = 0b10000000;while (1){if (g * g > n){g ^= c;}c >>= 1;if (c == 0){return g;}g |= c;}}I wrote a code to calculate 2550 times "sqrt()" and after "sqrt_8bit()" and measured time:Default "sqrt()" – Uno (328p, 16 MHz, 8bit) –95004microsecondsOptimyzed "sqrt_8bit()" – Uno (328p, 16 MHz, 8bit) –28552microseconds –3.33 times fasterDefault "sqrt()" – BluePill (STM32F103C8, 72 MHz, 32bit) –63971microseconds – have to admit I was a bit disappointed compared to UNO time, but then I tried new code:Optimyzed "sqrt_8bit()" – BluePill (STM32F103C8, 72 MHz, 32bit) –5390microseconds –almost12TIMES FASTER!"sqrt_16bit()" – BluePill (STM32F103C8, 72 MHz, 32bit) –9430microseconds –16bit still almost 7 times fasterWell, one can say that even with 12 times improvement it's not a big of a deal, but in my case MCU has to calculate root at least 50K/s, but with default "sqrt()" it could only calculate 25K/s. Now – 300K/s is not a problem, and that's a huge deal for meI2C and DMA:I still didn't find how to use hardware DMA directly, but I found a very simple solution for that and it even might use DMA. Instead of code I posted above I simply can use this code:Code:Wire.beginTransmission(0x60); // IC adressWire.write(0b10100010); // Write to the GRPPWM register AUTO_INCREMENT_BRIGHTNESSWire.write(RGBbuffer, 15);Wire.endTransmission();I found that Wire.write() actually does not send data, it only prepares a buffer for sending, Wire.endTransmission() sends data (I know, very obvious...).I hope this will help someone on their journey as well.Sadly I still didn't figure out how to use 2nd hardware I2C....Quote from: richardman on April 30, 2019, 08:23:37 pmIf I understand correctly ST MCU grouping is simple – it's basically same MCU but with different GPIO number, CPU power and some other features. It's like Nvidia GPUs – same line cores are architecturally same, but they have different features. It not hard to find specific MCU specs, I'm just too dumb to understand them

author: Siwastaja
author_rank: Super Contributor
timestamp: May 11, 2019, 03:01:52 pm
content: You found the important distinction that you seldom needpuremathematical functions; often approximations are good enough, and often performance matters.If your sqrt input range is limited, a lookup table would be even faster. Write a small helper program (in any language) to generate the table such as#define SQRT_LUT_LEN 256static const uint8_t sqrt_lut = {0,16,22,27,32,35...}; // example with 16x fixed point math.Generate it in a separate file, and #include it, so you can easily rewrite the lut file automatically (for example, if you change your LUT layout, like the length, resolution, fixed point division, etc... In general, autogenerated data is neater in a separate file (which is never manually modified) than copy-pasted within your human code.Lookup tables are great on simple microcontrollers where you typically have a fairly large amount of low-latency RAM (and even larger lowish-latency flash), and don't need to think too deeply about RAM latency, cache size, cache lines and alignment, etc...On a modern PC or a small embedded computer, a large lookup table might not fit the L1 cache, and obtaining a value from RAM might take tens to hundreds of CPU cycles - it's often faster to solve it using an algorithm. Worst, it's unpredictable. But on an MCU, RAM accesses tend to be around 1-2 cycles, FLASH accesses might be slower, but often not catastrophically so, so a LUT is almost always faster than an algorithm, even a large one, if it fits at all, of course.

author: NorthGuy
author_rank: Super Contributor
timestamp: May 11, 2019, 03:05:23 pm
content: Quote from: 3dgeo on May 11, 2019, 01:15:52 pmWhy 2550? If your input values are from 0 to 2550 then you only need 6 bits, not 8. In such case, you can initialize "c" and "g" to 0x20 instead of 0x80. Will be even faster.

author: 3dgeo
author_rank: Frequent Contributor
timestamp: May 11, 2019, 04:58:02 pm
content: Quote from: Siwastaja on May 11, 2019, 03:01:52 pmI optimized math to use only 8bit coordinates (0-254), I'm getting enough precision for what I need, I might increase it to 16bit just to see if it makes any different, but I'm fine with 8bits for now.I know about LUTs, I'm using few in the same project I needed this optimization. I was considering creating roots LUT table, but it would be huge – way too big to fit in MCU. Well, I guess I can write some clever code to optimize it, but I don't think it's worth doing – performance gain from root optimization is plenty not to be concern about it. And yes, I have made few Arduino scripts to create LUT tables – I'm too lazy to create them by typingQuote from: NorthGuy on May 11, 2019, 03:05:23 pmWhy 2550 – I already had testing code that runs root 255 times, I just encapsulated it with another 0-10 loop and got 2550. At that point I did not care how many times root calc ran, all I cared is to get descent time measurement and 2550 gave me that so I stick with it.I think Your math is a bit off – 2550 is 12bit number, not 6bit, and 8bit is 0-254. And in my project code (not testing code) I'm calculating Pythagorean theorem (a² + b² = c²) where "a" and "b" is 8bit inputs, so "c" can't be bigger, so 8bit (0-254) as well thus my posted code is correct.

author: NorthGuy
author_rank: Super Contributor
timestamp: May 11, 2019, 05:58:30 pm
content: Quote from: 3dgeo on May 11, 2019, 04:58:02 pmBut the square root of it is 6-bitQuote from: 3dgeo on May 11, 2019, 04:58:02 pmNot necessarily. For example, sqrt(254^2 + 254^2) = 359

author: Yansi
author_rank: Super Contributor
timestamp: May 11, 2019, 06:16:30 pm
content: Quote from: Siwastaja on April 25, 2019, 12:52:50 pmCompiler NEVER uses SIMD instructions by its own! If you want SIMD, than you need to hand optimize using the inline macros.

author: 3dgeo
author_rank: Frequent Contributor
timestamp: May 11, 2019, 08:54:37 pm
content: Quote from: NorthGuy on May 11, 2019, 05:58:30 pmYes, sorry, my bad, still, what I meant holds – root never goes above 8 bit due to "a" and "b" max numbers not reaching 8 bits.Currently I'm more focus on hardware, I can optimize code later, I just need to have general idea that hardware can do what I want it to do.Any advice on dual I2C?

author: Siwastaja
author_rank: Super Contributor
timestamp: May 12, 2019, 09:32:24 am
content: Quote from: Yansi on May 11, 2019, 06:16:30 pmIt appears you are right on the context of ARM microcontroller compilers, and mostly right in almost all other contexts as well; it seems "automatic vectorization" is still some super special fancy pancy shit. I have always written my own SIMD optimizations manually because I like explicit over implicit, and I don't trust compiler optimizations, but I incorrectly assumed compilers would do some basic, trivial SIMD optimizations on the highest optimization levels today - which still doesn't seem to be the case!Thanks for the correction.

author: NorthGuy
author_rank: Super Contributor
timestamp: May 12, 2019, 12:28:23 pm
content: Quote from: Siwastaja on May 12, 2019, 09:32:24 amIntel has a compiler that does automatic vectorization for x64.

author: 3dgeo
author_rank: Frequent Contributor
timestamp: May 18, 2019, 05:28:06 pm
content: I finally solved last problem – dual I2C peripherals. Again, solution is so simple that I feel like an idiotCode:TwoWire Wire2 (2, I2C_FAST_MODE); // I2C_2 (PB11-SDA, PB10-SCL )Wire2.begin();– treat "wire2" as regular "wire" (STM32DUINO core – hardware I2C).Code:TwoWire Wire2(PB11, PB10);Wire2.begin();– treat "wire2" as regular "wire" (ST core – may be only software I2C – doing testing...)