-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
stm32f0/i2c.c: calculate correct bus frequency #6694
Conversation
cb9d897
to
812e82a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for the same rate selection, it should result in identical SCL/SDA timings as currently exists (i.e. at 100kHz i2c clk, the SCL and SDA should be the same... I do understand this seeks to 'fix' devices which don't current output 100kHz where expected.. but changing ALL SDA/SCL timings seems unnecessary to achieve this outcome).
It would also need a check for if get_pclock_frequency(i2c) works for all of the relevant devices that use this function, and across the various i2c options on them:
- STM32F0
- STM32F7
- STM32G0
- STM32G4
- STM32H7
- STM32L4
We can't reuse values, they are too low, for high-frequency peripheral clocks. Old values adds up like (4+1) + 2 + (15+1) + (19+1) = 43 There is no room to change presc to match anything above 64Mhz. So, as a consequence of the hacky formula here, it just changed. I do not see a way to match existing timings precisely, but indeed - I can try "be close" to them. I can do
I literally did it
And checked all the constants here.
Thanks |
I sprinkle ashes on my head, I forgot that I cannot find that table, It is not included in the datasheet. I think I have two ways right now, one is just to specifically fix stm32h7 (plan B) or try to recalculate all timings according to the table above (plan A). BTW, Thank you for your help. I did like that and it just doesn't provide correct timings. So, scaling original values, give bad result:
This one should just work, but it also gives us wrong timings.
|
812e82a
to
4c75708
Compare
The datasheets are very much summary data for the functions available, not so much how to get that functionality working. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code itself looks good, a small step in the direction of 'better' from what was there before :)
nit would be that an enum could have been used instead of the magic numbers for the array indexes. i.e. to call out that [0] = 100kHz, [1] = 400kHz, [2] = 1000kHz
4c75708
to
547d2a7
Compare
What about something like this: --- a/src/stm32/stm32f0_i2c.c
+++ b/src/stm32/stm32f0_i2c.c
@@ -148,11 +148,26 @@ i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr)
gpio_peripheral(ii->sda_pin, ii->function | GPIO_OPEN_DRAIN, 1);
// Set 100Khz frequency and enable
- i2c->TIMINGR = ((0xB << I2C_TIMINGR_PRESC_Pos)
- | (0x13 << I2C_TIMINGR_SCLL_Pos)
- | (0xF << I2C_TIMINGR_SCLH_Pos)
- | (0x2 << I2C_TIMINGR_SDADEL_Pos)
- | (0x4 << I2C_TIMINGR_SCLDEL_Pos));
+ uint32_t nom_i2c_clock = 4000000; // 4mhz internal clock = 250ns ticks
+ uint32_t scll = 20; // 20 * 250ns = 5us
+ uint32_t sclh = 16; // 16 * 250ns = 4us
+ uint32_t sdadel = 2; // 2 * 250ns = 500ns
+ uint32_t scldel = 5; // 5 * 250ns = 1250ns
+
+ uint32_t presc = get_pclock_frequency((uint32_t)i2c) / nom_i2c_clock;
+ if (presc > 0x10) {
+ uint32_t adj = 2;
+ presc /= adj;
+ scll *= adj;
+ sclh *= adj;
+ sdadel *= adj;
+ scldel *= adj;
+ }
+ i2c->TIMINGR = (((presc - 1) << I2C_TIMINGR_PRESC_Pos)
+ | ((scll - 1) << I2C_TIMINGR_SCLL_Pos)
+ | ((sclh - 1) << I2C_TIMINGR_SCLH_Pos)
+ | (sdadel << I2C_TIMINGR_SDADEL_Pos)
+ | ((scldel - 1) << I2C_TIMINGR_SCLDEL_Pos));
i2c->CR1 = I2C_CR1_PE;
}
Along with: --- a/src/stm32/stm32g4.c
+++ b/src/stm32/stm32g4.c
@@ -12,7 +12,7 @@
#include "internal.h" // enable_pclock
#include "sched.h" // sched_main
-#define FREQ_PERIPH_DIV 1
+#define FREQ_PERIPH_DIV 2
#define FREQ_PERIPH (CONFIG_CLOCK_FREQ / FREQ_PERIPH_DIV)
// Map a peripheral address to its enable bits
@@ -142,7 +142,7 @@ clock_setup(void)
RCC->PLLCFGR |= RCC_PLLCFGR_PLLREN;
// Switch system clock to PLL
- RCC->CFGR = RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV1 | RCC_CFGR_PPRE2_DIV1
+ RCC->CFGR = RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV2 | RCC_CFGR_PPRE2_DIV2
| RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS_Msk) != RCC_CFGR_SWS_PLL)
; -Kevin |
Or, actually, how about this (along with the stm32g4.c change above): --- a/src/stm32/stm32f0_i2c.c
+++ b/src/stm32/stm32f0_i2c.c
@@ -148,11 +148,19 @@ i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr)
gpio_peripheral(ii->sda_pin, ii->function | GPIO_OPEN_DRAIN, 1);
// Set 100Khz frequency and enable
- i2c->TIMINGR = ((0xB << I2C_TIMINGR_PRESC_Pos)
- | (0x13 << I2C_TIMINGR_SCLL_Pos)
- | (0xF << I2C_TIMINGR_SCLH_Pos)
- | (0x2 << I2C_TIMINGR_SDADEL_Pos)
- | (0x4 << I2C_TIMINGR_SCLDEL_Pos));
+ uint32_t nom_i2c_clock = 8000000; // 8mhz internal clock = 125ns ticks
+ uint32_t scll = 40; // 40 * 125ns = 5us
+ uint32_t sclh = 32; // 32 * 125ns = 4us
+ uint32_t sdadel = 4; // 4 * 125ns = 500ns
+ uint32_t scldel = 10; // 10 * 125ns = 1250ns
+
+ uint32_t pclk = get_pclock_frequency((uint32_t)i2c);
+ uint32_t presc = DIV_ROUND_UP(pclk, nom_i2c_clock);
+ i2c->TIMINGR = (((presc - 1) << I2C_TIMINGR_PRESC_Pos)
+ | ((scll - 1) << I2C_TIMINGR_SCLL_Pos)
+ | ((sclh - 1) << I2C_TIMINGR_SCLH_Pos)
+ | (sdadel << I2C_TIMINGR_SDADEL_Pos)
+ | ((scldel - 1) << I2C_TIMINGR_SCLDEL_Pos));
i2c->CR1 = I2C_CR1_PE;
}
It'll run i2c slightly slower on stm32h7, but I doubt anyone will notice. -Kevin |
547d2a7
to
c7b63c4
Compare
Thanks. In general it looks fine. For what it is worth, I'd use the numbers from the stm document (ie, 4 for sclh). The actual transfer rate is dependent on the rise and fall time of the wires, which is directly tied to the strength of the pull-up resister on the wires - which is board dependent. So, if stm has a recommendation (of sorts) for what works on many boards - I'd just go with that. That said, this isn't a big concern for me either way, so if you'd prefer to stick with your current settings I'm okay with that. Along with this change should come an update to the Config_Reference.md document to indicate that some stm32 chips support 400K mode (see the blurb on i2c speeds near the very end of that document). -Kevin |
Signed-off-by: Timofey Titovets <[email protected]>
c7b63c4
to
14e96f0
Compare
I somewhat expected that controllers blindly do everything by timing. I am only worried about cases where sensors should use 400kHz, expect 400kHz like MPU9250, and I don't know how those will behave - I don't have one. So, let's give it a try with recommended values. Config_reference got updated. Thanks. |
Thanks. I committed this change. For future reference, the convention for the first line of the commit message is: -Kevin |
Signed-off-by: Timofey Titovets <[email protected]>
* stm32: allow 400Khz in stm32f0_i2c.c (Klipper3d#6694) Signed-off-by: Timofey Titovets <[email protected]> * stm32: Reduce peripheral clock speed on stm32g4 chips A 170mhz (or 150mhz) peripheral clock is too fast for some peripherals. Signed-off-by: Kevin O'Connor <[email protected]> commit hash: 8b7cc43 --------- Signed-off-by: Timofey Titovets <[email protected]> Co-authored-by: Timofey Titovets <[email protected]>
Right now i2c frequency is hardcoded, I think for stm32f0.
STM32f0 pclk is 48Mhz.
My STM32H7 has 400Mhz -> 100Mhz pclk.
it runs faster at around 200kHz
https://klipper.discourse.group/t/stm32h7-wrong-i2c-timings-200khz/18676
I added some actual timing calculations, but I can't get them precise.
100kHz, 1 / 0.0000085= 117647
400kHz, 1 / 0.0000025 = 400000
1000kHz, 1 / 0.0000013 = 769230
This is as close, as I can get to target values.