I2C on STM32F4  [SOLVED] - Page 1
Forum section: electronics / general

author: DELTA67
author_rank: Regular Contributor
timestamp: August 20, 2023, 03:31:54 pm
content: Hi all,For many days I'm struggling to get I2C working on an STM32F401CE (emulated in PROTEUS).USART works great, but I2C not yet! I'm using default values, HSI 16MHz as source clock and no prescalers.I need your help please.My code to initialize I2C1 and send a start condition is:Code:void i2c1_init(){// Enable clocks to the peripheralsRCC -> APB1ENR |= 1<<21;// Enable I2C1 clockRCC -> AHB1ENR |= 1<<1;// Enable GPIOB clock// set pins B6=SCL and B7=SDA to Alternate Function, Pull-up enabled, Open-Drain and high speedGPIOB -> MODER = 2<<2*7 | 2<<2*6;//MODER7/6[1:0] = 10 => Alternate function modeGPIOB -> OTYPER = 1<<1*7| 1<<1*6;// B6/7 = 1 => Output open-drainGPIOB -> OSPEEDR = 3<<2*7 | 3<<2*6;// OSPEEDR7/6[1:0] = 11 => High speedGPIOB -> PUPDR = 1<<2*7 | 1<<2*6;// PUPDR7/6[1:0] = 01 => Pull-upGPIOB -> AFRL = (4 << 4*7) | (4 << 4*6);//AFRL7/6[3:0] = 0100 => AF4 (I2C1)// initialization steps:I2C1 -> CR1 |= 1<<15;// SWRST (bit15) = 1 => Enable the software resetI2C1 -> CR1 &= ~(1<<15);// then immediately resetting the SWRST bit.I2C1 -> CR2 = 16;// FREQ[5:0] (Bits 5:0) = 16 => Peripheral clock frequency = 16 MHz// other bits = 0 => Interrupts and DMA disabled//I2C1 -> OAR1 = 0x30;// Own address in slave mode only// set SCL clock: Thigh = Tlow = CCR * TPCLK1 (here TPCLK1 = 1/16 MHz)// For 100kHz, Thigh = Tlow = 5uS => CCR = (5/0.0625) = 80I2C1 -> CCR = 80;// F/S (Bit 15) = 0 => standard master mode// CCR[11:0] (Bits 11:0) = 80 => SCL clock = 100kHzI2C1 -> TRISE |= 11;// TRISE[5:0] (Bits 5:0) = 9 => Maximum rise time in standard modeI2C1 -> CR1 |= 1;// PE (bit0) = 1 => Enable the peripheral}// send a start condition and slave addressvoid I2C1_Start(char address){uint16_t reg;//I2C1->CR1 |= (1<<10);// Enable the ACKI2C1 -> CR1 |= 1<<8;// START (Bit 8) = 1 => start generation (and enter master mode)while(!(I2C1 -> SR1 & 1<<0));// wait until SB (Bit 0) = 1 => Start condition generatedreg = I2C1 -> SR1;// MUST read SR1 reg first// then send the address to clear SB bitI2C1 -> DR = (address << 1) | 0x00;// write address in DR regwhile(!(I2C1 -> SR1 & 1<<1));// wait until ADDR (Bit 1) = 1 => End of address transmission// ADDR bit is cleared by software reading SR1 followed by reading SR2reg = I2C1 -> SR1;reg = I2C1 -> SR2;//In datasheet p499: TxE is not set during address phase ?!//while(!(I2C1 -> SR1 & 1<<7));// wait until TxE (Bit 7) = 1 => Data register empty}void main(){i2c1_init();I2C1_Start(0x60);for (;;) ;}Full project is attached.PLEASE helpEDIT:To build the code, decomment line33 in "periph.h".EDIT2:SOLVED.The I2C module for STM32F401CE in PROTEUS is buggy!!I tested the one for ATMEGA328 it works great.Anyway, PROTEUS is really a marvellous tool.

author: eutectique
author_rank: Frequent Contributor
timestamp: August 20, 2023, 05:43:30 pm
content: Quote from: DELTA67 on August 20, 2023, 03:31:54 pmIs there any good reason not to useC macros?Is there any good reason not to use STM header files?

author: DavidAlfa
author_rank: Super Contributor
timestamp: August 20, 2023, 06:40:56 pm
content: OMG, at least use the cmsis headers and macros!

author: DELTA67
author_rank: Regular Contributor
timestamp: August 20, 2023, 08:38:34 pm
content: What's wrong with this style of programming?I LOVE bare metal C and assembler. I don't like ready to use libraries. I find more plaisir when working at the bit level. I do this just for fun not for work.I've followed the datasheet step by step and I don't know what I'm missing!If some one can help, I'll be very grateful.Thanks

author: DavidAlfa
author_rank: Super Contributor
timestamp: August 20, 2023, 09:20:14 pm
content: What's wrong ? Because human DO make mistakes.Just an example:Are you sure 1<<15 is SWRST? Or maybe 1<<14. Or 1<<13?Or wait 2 months and open this project again.The comments are just saying "Trust me bro", if not working you'll have a very hard time checking everything out.And now it doesn't work you ask for help (Which is ok!).But.. Maybe your code is wrong... or one of these bits are mistaken ?So now you have 40 hardcoded bits everywhere.That's not asking for help, that's almost an audit, requiring anyone to open the RM and check every reg bit to make sure everything is ok...A total waste of time.But using the CMSIS headers it's much, much better:I2C1 -> CR1 |= I2C1_CR1_SWRST;I2C1_CR1_SWRST instantly says it all:- It's for I2C1- It's for CR1- It's the SWRST bitSo any mistake can be easlily detected:I2C1 -> CR1 |= I2C2_CR1_SWRST;I2C1 -> CR1 |= I2C1_CR1_POTATO;I2C1 -> CR2 |= I2C1_CR1_SWRST;I'm just saying, make your/our life easier!CMSIS headers are made by ARM itself, it isn't any kind of library, you'll still be coding at baremetal level.

author: DELTA67
author_rank: Regular Contributor
timestamp: August 20, 2023, 09:59:21 pm
content: Thanks DavidAlpha for your responses.I'm sorry, I appologize for abusing your precious time.Apart of enabling GPIOB clock and configure SDA and SCL lines, enabling I2C1 clock in RCC_APB1ENR reg and enabling the peripheral by setting the PE bit in I2C_CR1 reg is there any other thing to enable?

author: DavidAlfa
author_rank: Super Contributor
timestamp: August 20, 2023, 10:55:33 pm
content: No need to reinvent the wheel.Simply search "stm32 F4 baremetal i2c":https://github.com/fcayci/stm32f4-bare-metal/blob/master/projects/i2c/i2c.c

author: DELTA67
author_rank: Regular Contributor
timestamp: August 21, 2023, 04:55:48 am
content: Thanks again DavidAlpha for your help.I've already tested the "INITIALIZATION PART" of that code ( and many other projects), with no success!I debugged the code in PROTEUS, all the registers have the expected values but the start condition is not generated.

author: Berni
author_rank: Super Contributor
timestamp: August 21, 2023, 05:23:34 am
content: The MCU simulators often don't perfectly emulate all the peripherals. It is possible the code works just fine on a real STM32

author: DELTA67
author_rank: Regular Contributor
timestamp: August 21, 2023, 08:27:00 am
content: Thanks BerniAs I've said, I tested only the "INITIALIZATION PART" of that code not all the project, perhaps I need to do some thing relative to CLOCKs, I2C module is fed with APB1 clock.In principle PROTEUS simulates the I2C peripheral very well. I tested the USART and it works.I'm using default values, HSI 16MHz as source clock and no prescalers.I've an STM32F411 based board but no logic analyzer, How to test using a basic oscillo?Perhaps I need to send some data continuosly and see the activity on the I2C lines?

author: wek
author_rank: Frequent Contributor
timestamp: August 21, 2023, 10:24:21 am
content: > I've an STM32F411 based board but no logic analyzer, How to test using a basic oscillo?On a two-channel oscilloscope, you would see both SCL and SDA, and you need nothing more.JW

author: Berni
author_rank: Super Contributor
timestamp: August 21, 2023, 10:43:34 am
content: Yeah clocks have to be configured of course. On most STM32s all the peripherals start with a disabled bus clock to save power. Failing to turn on clocks is easily sported by looking at the whole peripherals register values(be it using the peripheral view in CubeIDE, or just placing a struct of the peripheral registers into the Watch window). You tend to see a lot of 0s as the values and writing to a register results in no change. Easy fix takes one line to turn the clock on.Best way to actually setup the clocks speeds is using the CubeMX tool, not only does it let you adjust the potentially very complicated clock paths in a nice GUI, but it also calculates the MHz at each stage and warns you if any clocks are too high or too low to work correctly. You can let it generate the code for you and then just copy paste out the parts of the code you want to use.Oscilloscope is the perfect tool for checking it. Tho it does need to be a digital scope, the analog scope won't show it in a usable way. If you don't have a decent digital scope i would highly suggest you go pick up a USB logic analyzer. Here is a nice cheep Chinese one that i usehttp://www.qdkingst.com/enI have plenty of nice scopes, but for analyzing communications it is more convenient to use.(Practically infinite sample memory, easier to navigate with a mouse, supports all sorts of protocol decoders in parallel...)

author: langwadt
author_rank: Super Contributor
timestamp: August 21, 2023, 11:14:02 am
content: Quote from: Berni on August 21, 2023, 10:43:34 amfor I2C something likehttps://www.ebay.com/itm/134692438303and pulseview is plenty

author: DELTA67
author_rank: Regular Contributor
timestamp: August 21, 2023, 01:43:31 pm
content: Thanks Berni, wek and langwadt.When debugging I found that the program stucks at the line:Code:while(!(I2C1 -> SR1 & 1<<0));// wait until SB (Bit 0) = 1 => Start condition generatedThe bit "start" in I2C_CR1 is set as expected and also the bit MSL in I2C_SR2 which means that the peripheral is in master mode.But the bit SB still cleared and the start condition is not generated on the bus .

author: DavidAlfa
author_rank: Super Contributor
timestamp: August 21, 2023, 01:47:37 pm
content: That'sexactlya start condition:You must write the slave address next!Simply:- Search a working example.- Simulate it.- It works? Try your own.- It doesn't? Not rare, proteus MCU models have plenty of bugs.

author: DELTA67
author_rank: Regular Contributor
timestamp: August 21, 2023, 02:19:16 pm
content: I said that the start condition is NOT generated as expected despite the bit MSL is set !!And the SB bit stays cleared so the program stucks waiting for it to goes high.Have you a simple working example for F401CE or F411CE please?

author: wek
author_rank: Frequent Contributor
timestamp: August 21, 2023, 02:38:18 pm
content: QuoteTo me, this also sounds as that you've observed what is in the brackets, and interpreted it as "not start condition".So, what did you exactly observe?If you set both pins as GPIO Out in MODER (other things unchanged), can you generate the start condition - or for that matter, any pulses combination - on the bus "manually"?JW

author: wek
author_rank: Frequent Contributor
timestamp: August 21, 2023, 03:00:56 pm
content: Code:d:\tmp\tmp>maked:/PROGRA~1/ARMTools/launchpad.net_gcc-arm-embedded_5.4/bin/arm-none-eabi-as -g crt0.s -o crt0.od:/PROGRA~1/ARMTools/launchpad.net_gcc-arm-embedded_5.4/bin/arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Os -c -o i2c.o i2c.ci2c.c: In function 'i2c1_init':i2c.c:19:2: error: 'I2C1' undeclared (first use in this function)I2C1 -> CR1 |= 1<<15; // SWRST (bit15) = 1 => Enable the software reset^i2c.c:19:2: note: each undeclared identifier is reported only once for each function it appears ini2c.c: In function 'I2C1_Start':i2c.c:39:2: error: 'I2C1' undeclared (first use in this function)I2C1 -> CR1 |= 1<<8; // START (Bit 8) = 1 => start generation (and enter master mode)^make: *** [i2c.o] Error 1d:\tmp\tmp>

author: DELTA67
author_rank: Regular Contributor
timestamp: August 21, 2023, 04:08:36 pm
content: Quote from: wek on August 21, 2023, 02:38:18 pm

author: DELTA67
author_rank: Regular Contributor
timestamp: August 21, 2023, 04:20:00 pm
content: The corrected file with binary is attached bellow.

author: wek
author_rank: Frequent Contributor
timestamp: August 21, 2023, 06:22:13 pm
content: >> So, what did you exactly observe?> No thing!What does that mean? SDA and SCL are surely at some levels.I happen to have a hardware on my desk, with STM32F407, which in all respects of this program has identical GPIOB/I2C1, and I happen to have connected an EEPROM on PB6/PB7. So I simply burned the elf into that hardware, run, and it produced (probably) a START and transmission of address, after which it stuck in the loop waiting for I2C_SR1.ADDR (which never gets set as there's nothing which would ACK the given address - you should check also for I2C_SR1.AF, and I recommend you to add also timeouts later).(probably) means that I observed SDA and SCL separately, as the given bus is buried deeply in the device in question and I have no intention to bring out the bus to be probed properly, so I just stuck a oscilloscope probe onto first then second pullup.So, if you observe something else, I'd say it's hardware failure. You may want to use a "known good" hardware such as Nucleo or Disco boards, together with some means to debug through SWD.JWPS. Describe your hardware. What is connected to the bus - slaves, pullups?

author: DELTA67
author_rank: Regular Contributor
timestamp: August 21, 2023, 08:31:06 pm
content: Quote from: wek on August 21, 2023, 06:22:13 pmSCL and SDA stay high.I'm using PROTEUS for simulation and debugging using included logic analyzer. I've already used it with an ATMEGA328 and it works.I used also a BlackPIll with F411, and probed SCL and SDA with an oscillo, no activity detected.At the moment I need only a start condition with address transmission as a proof that the code works.It's not mandatory to have a device connected to the bus in order to have a start condition and address transmission, the lines are already pulled-up by the internal resistors on PB6 and PB7.Good news if the code works for you! The address of an EEPROM if A2A1A0 =000 is 0x50 in 7bits format and 0xA0 in 8bits. Can you give it a try please?

author: DavidAlfa
author_rank: Super Contributor
timestamp: August 21, 2023, 08:42:48 pm
content: STM32 addition in Proteus is pretty recent, it's much more complex than AVRs, so expect bugs!You can't say "it works in AVR so it makes sense that STM32 also does".Just get a cheap board, a stlink and the analyzer. All for $10 or less.Another code example:https://dk7ih.de/an-introduction-to-bare-metal-programming-the-arm-cortex-m4r-mcus-lesson-10-i%C2%B2c/

author: DELTA67
author_rank: Regular Contributor
timestamp: August 21, 2023, 10:04:07 pm
content: Thanks DavidAlpha for the link which I've already read the content many times!I've tried their initialization code also with no success.Yes I have a BLackPill with HID bootloader and a 10MHz USB picoscope.

author: DavidAlfa
author_rank: Super Contributor
timestamp: August 21, 2023, 10:21:48 pm
content: I simulated that example with a 24c02 (Addr = 0xA0) and it partially worked.Sending start then stop crashed somewhere, it didn't like the elf file so I loaded the hex, thus I couldn't step the program.But I could see start, address, byte 0, stop.It should inmediately send a start and read the data, but nope.

author: DELTA67
author_rank: Regular Contributor
timestamp: August 21, 2023, 10:23:30 pm
content: Ok, I'll retry again.

author: DELTA67
author_rank: Regular Contributor
timestamp: August 22, 2023, 08:52:05 am
content: Quote from: DavidAlfa on August 21, 2023, 10:21:48 pmHow did you built it? It seems that it's based on the standard peripheral driver. The keil uvision 5 uses the CUBEMX HAL.can you attach your project and all the files used in simulation please?

author: DavidAlfa
author_rank: Super Contributor
timestamp: August 22, 2023, 01:47:45 pm
content: The link I posted earlier only uses CMSIS headers, no peripheral library of any kind, should work just fine.Have you tried?I'm using CubeIDE.

author: DELTA67
author_rank: Regular Contributor
timestamp: August 22, 2023, 04:35:29 pm
content: I've found a working example for STM32F4 here:https://blog.embeddedexpert.io/?p=613I've made some modifications in order to get it working for the BLack PILL (F411):* In main.c:- I commented the clock config in order to use default 16MHz HSI clockCode://SysClockConfig();* In i2c.c:- I enabled the pull-ups for PB8 and PB9Code:GPIOB->PUPDR|= 1<<16 | 1<<18;- I modified the values of CR2, CCR and TRISE registers:Code:I2C1->CR2|= 16;// APB1 = 16MHzI2C1->CCR|= 80;I2C1->TRISE= 11;Et voilà !!But when testing in PROTEUS it does not work as before. No start condition and no address transmission.Later, I'll try with a lower frequency for APB1 (8MHz) and lower freq for SCL (50kHz) and see.Thanks for ALL for your help

author: DavidAlfa
author_rank: Super Contributor
timestamp: August 22, 2023, 04:41:47 pm
content: The model is definitely bugged.As in the datasheet, I2C SR2 MSL bit goes to 0 (Slave mode) when a stop is detected.I have some code using that flag, it worked perfectly in real hardware.MSL never goes back to 0 in Proteus.Just use LL. Does exactly what you would do by hand. No overhead, no bloat. Just skip rewriting the wheel.This is my function:Code:ErrorStatus I2C1_MasterTransmit(uint8_t DevAddress, uint8_t *pData, uint16_t Size){ErrorStatus status = SUCCESS;LL_I2C_GenerateStartCondition(I2C1);while(LL_I2C_IsActiveFlag_SB(I2C1) != 1);LL_I2C_TransmitData8(I2C1, DevAddress);while(!LL_I2C_IsActiveFlag_ADDR(I2C1) || !LL_I2C_IsActiveFlag_TXE(I2C1)){if(LL_I2C_IsActiveFlag_AF(I2C1))break;}LL_I2C_ClearFlag_ADDR(I2C1);if(LL_I2C_IsActiveFlag_AF(I2C1)){status = ERROR;LL_I2C_ClearFlag_AF(I2C1);}else{while(Size--){LL_I2C_TransmitData8(I2C1, *pData++);while(!LL_I2C_IsActiveFlag_TXE(I2C1));if(LL_I2C_IsActiveFlag_AF(I2C1)){status = ERROR;break;}}}while(!LL_I2C_IsActiveFlag_BTF(I2C1));LL_I2C_GenerateStopCondition(I2C1);while(LL_I2C_IsActiveFlag_MSL(I2C1));return status;}Code:__STATIC_INLINE void LL_I2C_GenerateStartCondition(I2C_TypeDef *I2Cx){SET_BIT(I2Cx->CR1, I2C_CR1_START);}Code:__STATIC_INLINE void LL_I2C_GenerateStopCondition(I2C_TypeDef *I2Cx){SET_BIT(I2Cx->CR1, I2C_CR1_STOP);}Code:__STATIC_INLINE void LL_I2C_TransmitData8(I2C_TypeDef *I2Cx, uint8_t Data){MODIFY_REG(I2Cx->DR, I2C_DR_DR, Data);}Code:__STATIC_INLINE uint32_t LL_I2C_IsActiveFlag_ADDR(I2C_TypeDef *I2Cx){return (READ_BIT(I2Cx->SR1, I2C_SR1_ADDR) == (I2C_SR1_ADDR));}And so on. That's LL. Inlining avoids any slow calls. Simple functions and calls, nothing convoluted.

author: DELTA67
author_rank: Regular Contributor
timestamp: August 22, 2023, 09:45:31 pm
content: Quote from: DavidAlfa on August 22, 2023, 04:41:47 pmI confirm that.I've tested with different APB1 frequencies (16, 8 and 4MHz) and SCL frequencies 100 and 50kHz.Thanks David alpha, wek and Others.How to tag this thread  ?

author: DavidAlfa
author_rank: Super Contributor
timestamp: August 22, 2023, 10:03:22 pm
content: Edit the title in the first message?

author: DELTA67
author_rank: Regular Contributor
timestamp: August 23, 2023, 08:31:03 am
content: Ok, thanks.